UNPKG

@codama/validators

Version:

Validator visitors for the Codama framework

1 lines 20.1 kB
{"version":3,"sources":["../src/index.ts","../src/getValidationItemsVisitor.ts","../src/ValidationItem.ts","../src/throwValidatorItemsVisitor.ts"],"sourcesContent":["export * from './getValidationItemsVisitor';\nexport * from './throwValidatorItemsVisitor';\nexport * from './ValidationItem';\n","import { camelCase, getAllInstructionArguments, isNode } from '@codama/nodes';\nimport {\n extendVisitor,\n getResolvedInstructionInputsVisitor,\n LinkableDictionary,\n mergeVisitor,\n NodeStack,\n pipe,\n recordLinkablesOnFirstVisitVisitor,\n recordNodeStackVisitor,\n visit,\n Visitor,\n} from '@codama/visitors-core';\n\nimport { ValidationItem, validationItem } from './ValidationItem';\n\nexport function getValidationItemsVisitor(): Visitor<readonly ValidationItem[]> {\n const linkables = new LinkableDictionary();\n const stack = new NodeStack();\n\n return pipe(\n mergeVisitor(\n () => [] as readonly ValidationItem[],\n (_, items) => items.flat(),\n ),\n v => recordLinkablesOnFirstVisitVisitor(v, linkables),\n v => recordNodeStackVisitor(v, stack),\n v =>\n extendVisitor(v, {\n visitAccount(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Account has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitDefinedType(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Defined type has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitDefinedTypeLink(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Pointing to a defined type with no name.', node, stack));\n } else if (!linkables.has(stack.getPath(node.kind))) {\n items.push(\n validationItem(\n 'error',\n `Pointing to a missing defined type named \"${node.name}\"`,\n node,\n stack,\n ),\n );\n }\n return [...items, ...next(node)];\n },\n\n visitEnumEmptyVariantType(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Enum variant has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitEnumStructVariantType(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Enum variant has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitEnumTupleVariantType(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Enum variant has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitEnumType(node, { next }) {\n const items = [] as ValidationItem[];\n if (node.variants.length === 0) {\n items.push(validationItem('warn', 'Enum has no variants.', node, stack));\n }\n node.variants.forEach(variant => {\n if (!variant.name) {\n items.push(validationItem('error', 'Enum variant has no name.', node, stack));\n }\n });\n return [...items, ...next(node)];\n },\n\n visitError(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Error has no name.', node, stack));\n }\n if (typeof node.code !== 'number') {\n items.push(validationItem('error', 'Error has no code.', node, stack));\n }\n if (!node.message) {\n items.push(validationItem('warn', 'Error has no message.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitInstruction(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Instruction has no name.', node, stack));\n }\n\n // Check for duplicate account names.\n const accountNameHistogram = new Map<string, number>();\n node.accounts.forEach(account => {\n if (!account.name) {\n items.push(validationItem('error', 'Instruction account has no name.', node, stack));\n return;\n }\n const count = (accountNameHistogram.get(account.name) ?? 0) + 1;\n accountNameHistogram.set(account.name, count);\n // Only throw an error once per duplicated names.\n if (count === 2) {\n items.push(\n validationItem(\n 'error',\n `Account name \"${account.name}\" is not unique in instruction \"${node.name}\".`,\n node,\n stack,\n ),\n );\n }\n });\n\n // Check for cyclic dependencies in account defaults.\n const cyclicCheckVisitor = getResolvedInstructionInputsVisitor();\n try {\n visit(node, cyclicCheckVisitor);\n } catch (error) {\n items.push(validationItem('error', (error as Error).message, node, stack));\n }\n\n // Check args.\n const names = getAllInstructionArguments(node).map(({ name }) => camelCase(name));\n const duplicates = names.filter((e, i, a) => a.indexOf(e) !== i);\n const uniqueDuplicates = [...new Set(duplicates)];\n const hasConflictingNames = uniqueDuplicates.length > 0;\n if (hasConflictingNames) {\n items.push(\n validationItem(\n 'error',\n `The names of the following instruction arguments are conflicting: ` +\n `[${uniqueDuplicates.join(', ')}].`,\n node,\n stack,\n ),\n );\n }\n\n // Check arg defaults.\n getAllInstructionArguments(node).forEach(argument => {\n const { defaultValue } = argument;\n if (isNode(defaultValue, 'accountBumpValueNode')) {\n const defaultAccount = node.accounts.find(account => account.name === defaultValue.name);\n if (defaultAccount && defaultAccount.isSigner !== false) {\n items.push(\n validationItem(\n 'error',\n `Argument ${argument.name} cannot default to the bump attribute of ` +\n `the [${defaultValue.name}] account as it may be a Signer.`,\n node,\n stack,\n ),\n );\n }\n }\n });\n\n return [...items, ...next(node)];\n },\n\n visitProgram(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Program has no name.', node, stack));\n }\n if (!node.publicKey) {\n items.push(validationItem('error', 'Program has no public key.', node, stack));\n }\n if (!node.version) {\n items.push(validationItem('warn', 'Program has no version.', node, stack));\n }\n if (!node.origin) {\n items.push(validationItem('info', 'Program has no origin.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitStructFieldType(node, { next }) {\n const items = [] as ValidationItem[];\n if (!node.name) {\n items.push(validationItem('error', 'Struct field has no name.', node, stack));\n }\n return [...items, ...next(node)];\n },\n\n visitStructType(node, { next }) {\n const items = [] as ValidationItem[];\n\n // Check for duplicate field names.\n const fieldNameHistogram = new Map<string, number>();\n node.fields.forEach(field => {\n if (!field.name) return; // Handled by TypeStructField\n const count = (fieldNameHistogram.get(field.name) ?? 0) + 1;\n fieldNameHistogram.set(field.name, count);\n // Only throw an error once per duplicated names.\n if (count === 2) {\n items.push(\n validationItem(\n 'error',\n `Struct field name \"${field.name}\" is not unique.`,\n field,\n stack,\n ),\n );\n }\n });\n return [...items, ...next(node)];\n },\n\n visitTupleType(node, { next }) {\n const items = [] as ValidationItem[];\n if (node.items.length === 0) {\n items.push(validationItem('warn', 'Tuple has no items.', node, stack));\n }\n return [...items, ...next(node)];\n },\n }),\n );\n}\n","import { Node } from '@codama/nodes';\nimport { NodePath, NodeStack } from '@codama/visitors-core';\n\nexport const LOG_LEVELS = ['debug', 'trace', 'info', 'warn', 'error'] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\nexport type ValidationItem = {\n level: LogLevel;\n message: string;\n node: Node;\n path: NodePath;\n};\n\nexport function validationItem(\n level: LogLevel,\n message: string,\n node: Node,\n path: NodePath | NodeStack,\n): ValidationItem {\n return {\n level,\n message,\n node,\n path: Array.isArray(path) ? path : (path as NodeStack).getPath(),\n };\n}\n\nexport const getLevelIndex = (level: LogLevel): number => LOG_LEVELS.indexOf(level);\n","import { CODAMA_ERROR__VISITORS__FAILED_TO_VALIDATE_NODE, CodamaError } from '@codama/errors';\nimport { NodeKind } from '@codama/nodes';\nimport { mapVisitor, Visitor } from '@codama/visitors-core';\n\nimport { getLevelIndex, LogLevel, ValidationItem } from './ValidationItem';\n\nexport function throwValidatorItemsVisitor<TNodeKind extends NodeKind = NodeKind>(\n visitor: Visitor<readonly ValidationItem[], TNodeKind>,\n throwLevel: LogLevel = 'error',\n): Visitor<void, TNodeKind> {\n return mapVisitor(visitor, validationItems => {\n const levelHistogram = [...validationItems]\n .sort((a, b) => getLevelIndex(b.level) - getLevelIndex(a.level))\n .reduce(\n (acc, item) => {\n acc[item.level] = (acc[item.level] ?? 0) + 1;\n return acc;\n },\n {} as Record<LogLevel, number>,\n );\n const maxLevel = Object.keys(levelHistogram)\n .map(level => getLevelIndex(level as LogLevel))\n .sort((a, b) => b - a)[0];\n\n if (maxLevel >= getLevelIndex(throwLevel)) {\n const formattedHistogram = Object.keys(levelHistogram)\n .map(level => `${level}s: ${levelHistogram[level as LogLevel]}`)\n .join(', ');\n throw new CodamaError(CODAMA_ERROR__VISITORS__FAILED_TO_VALIDATE_NODE, {\n formattedHistogram,\n validationItems,\n });\n }\n });\n}\n"],"mappings":"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","names":["import_visitors_core"]}