diff --git a/packages/errors/src/codes.ts b/packages/errors/src/codes.ts index d4b188b91..2ca79f33f 100644 --- a/packages/errors/src/codes.ts +++ b/packages/errors/src/codes.ts @@ -54,6 +54,7 @@ export const CODAMA_ERROR__VISITORS__INSTRUCTION_ENUM_ARGUMENT_NOT_FOUND = 12000 export const CODAMA_ERROR__VISITORS__CANNOT_FLATTEN_STRUCT_WITH_CONFLICTING_ATTRIBUTES = 1200010; export const CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND = 1200011; export const CODAMA_ERROR__VISITORS__CANNOT_REMOVE_LAST_PATH_IN_NODE_STACK = 1200012; +export const CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE = 1200013; // Anchor-related errors. // Reserve error codes in the range [2100000-2100999]. @@ -165,4 +166,5 @@ export type CodamaErrorCode = | typeof CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY | typeof CODAMA_ERROR__VISITORS__INVALID_NUMBER_WRAPPER | typeof CODAMA_ERROR__VISITORS__INVALID_PDA_SEED_VALUES + | typeof CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE | typeof CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND; diff --git a/packages/errors/src/context.ts b/packages/errors/src/context.ts index 811de3145..6272f4dca 100644 --- a/packages/errors/src/context.ts +++ b/packages/errors/src/context.ts @@ -18,6 +18,7 @@ import { PdaNode, PdaSeedValueNode, ProgramNode, + ProvidedNode, StructFieldTypeNode, } from '@codama/node-types'; @@ -77,6 +78,7 @@ import { CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY, CODAMA_ERROR__VISITORS__INVALID_NUMBER_WRAPPER, CODAMA_ERROR__VISITORS__INVALID_PDA_SEED_VALUES, + CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND, CodamaErrorCode, } from './codes'; @@ -315,6 +317,12 @@ export type CodamaErrorContext = DefaultUnspecifiedErrorContextToUndefined<{ pda: PdaNode; pdaName: IdentifierString; }; + [CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE]: { + expectedKinds: NodeKind[]; + key: IdentifierString; + provider: ProvidedNode; + providedKind: NodeKind; + }; [CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND]: { key: string; }; diff --git a/packages/errors/src/messages.ts b/packages/errors/src/messages.ts index db56cccc9..616618d82 100644 --- a/packages/errors/src/messages.ts +++ b/packages/errors/src/messages.ts @@ -59,6 +59,7 @@ import { CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY, CODAMA_ERROR__VISITORS__INVALID_NUMBER_WRAPPER, CODAMA_ERROR__VISITORS__INVALID_PDA_SEED_VALUES, + CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND, CodamaErrorCode, } from './codes'; @@ -151,5 +152,7 @@ export const CodamaErrorMessages: Readonly<{ [CODAMA_ERROR__VISITORS__INVALID_NUMBER_WRAPPER]: 'Invalid number wrapper kind [$wrapper].', [CODAMA_ERROR__VISITORS__INVALID_PDA_SEED_VALUES]: 'Invalid seed values for PDA [$pdaName] in instruction [$instructionName].', + [CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE]: + 'The value provided for [$key] is a [$providedKind], expected one of [$expectedKinds].', [CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND]: 'Cannot find key [$key] in RenderMap.', }; diff --git a/packages/nodes/src/TextNode.ts b/packages/nodes/src/TextNode.ts new file mode 100644 index 000000000..77a3454ca --- /dev/null +++ b/packages/nodes/src/TextNode.ts @@ -0,0 +1,12 @@ +import type { TextNode } from '@codama/node-types'; + +/** + * Read the plain-string content of a `string | TextNode` value. + * + * `docs`, `instructionStatusNode.message`, display labels and intents are + * all `string | TextNode` — a plain string or the rich {@link textNode} + * arm. This returns the underlying text for either form. + */ +export function getTextNodeContent(value: string | TextNode): string { + return typeof value === 'string' ? value : value.content; +} diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index cf543ee9b..9c4a3ce61 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -14,6 +14,7 @@ export * from './EnumTypeNode'; export * from './InstructionNode'; export * from './Node'; export * from './ProgramNode'; +export * from './TextNode'; export * from './TypeNode'; // Legacy plural-noun aliases preserved for API stability. Each maps to diff --git a/packages/visitors-core/src/LinkableDictionary.ts b/packages/visitors-core/src/LinkableDictionary.ts index b55ce47b8..1d539a87e 100644 --- a/packages/visitors-core/src/LinkableDictionary.ts +++ b/packages/visitors-core/src/LinkableDictionary.ts @@ -1,10 +1,9 @@ import { CODAMA_ERROR__LINKED_NODE_NOT_FOUND, CodamaError } from '@codama/errors'; import { AccountNode, - CamelCaseString, DefinedTypeNode, + IdentifierString, InstructionAccountNode, - InstructionArgumentNode, InstructionNode, isNode, LinkNode, @@ -25,7 +24,6 @@ export type LinkableNode = | AccountNode | DefinedTypeNode | InstructionAccountNode - | InstructionArgumentNode | InstructionNode | PdaNode | ProgramNode; @@ -34,7 +32,6 @@ export const LINKABLE_NODES: LinkableNode['kind'][] = [ 'accountNode', 'definedTypeNode', 'instructionAccountNode', - 'instructionArgumentNode', 'instructionNode', 'pdaNode', 'programNode', @@ -44,7 +41,6 @@ export type GetLinkableFromLinkNode = { accountLinkNode: AccountNode; definedTypeLinkNode: DefinedTypeNode; instructionAccountLinkNode: InstructionAccountNode; - instructionArgumentLinkNode: InstructionArgumentNode; instructionLinkNode: InstructionNode; pdaLinkNode: PdaNode; programLinkNode: ProgramNode; @@ -60,7 +56,6 @@ type ProgramDictionary = { type InstructionDictionary = { accounts: Map>; - arguments: Map>; instruction: NodePath; }; @@ -81,8 +76,6 @@ export class LinkableDictionary { programDictionary.pdas.set(linkableNode.identifier, linkablePath); } else if (instructionDictionary && isNodePath(linkablePath, 'instructionAccountNode')) { instructionDictionary.accounts.set(linkableNode.identifier, linkablePath); - } else if (instructionDictionary && isNodePath(linkablePath, 'instructionArgumentNode')) { - instructionDictionary.arguments.set(linkableNode.identifier, linkablePath); } return this; @@ -121,8 +114,6 @@ export class LinkableDictionary { return programDictionary.definedTypes.get(linkNode.identifier) as LinkablePath; } else if (isNode(linkNode, 'instructionAccountLinkNode')) { return instructionDictionary?.accounts.get(linkNode.identifier) as LinkablePath; - } else if (isNode(linkNode, 'instructionArgumentLinkNode')) { - return instructionDictionary?.arguments.get(linkNode.name) as LinkablePath; } else if (isNode(linkNode, 'instructionLinkNode')) { return instructionDictionary?.instruction as LinkablePath; } else if (isNode(linkNode, 'pdaLinkNode')) { @@ -155,8 +146,6 @@ export class LinkableDictionary { return programDictionary.definedTypes.has(linkNode.identifier); } else if (isNode(linkNode, 'instructionAccountLinkNode')) { return !!instructionDictionary && instructionDictionary.accounts.has(linkNode.identifier); - } else if (isNode(linkNode, 'instructionArgumentLinkNode')) { - return !!instructionDictionary && instructionDictionary.arguments.has(linkNode.name); } else if (isNode(linkNode, 'instructionLinkNode')) { return programDictionary.instructions.has(linkNode.identifier); } else if (isNode(linkNode, 'pdaLinkNode')) { @@ -202,7 +191,6 @@ export class LinkableDictionary { if (!instructionDictionary) { instructionDictionary = { accounts: new Map(), - arguments: new Map(), instruction: getNodePathUntilLastNode(linkablePath, 'instructionNode')!, }; programDictionary.instructions.set(instructionNode.identifier, instructionDictionary); @@ -213,7 +201,7 @@ export class LinkableDictionary { private getProgramDictionary(linkPath: NodePath): ProgramDictionary | undefined { const linkNode = getLastNodeFromPath(linkPath); - let programName: CamelCaseString | undefined = undefined; + let programName: IdentifierString | undefined = undefined; if (isNode(linkNode, 'programLinkNode')) { programName = linkNode.identifier; } else if ('program' in linkNode) { @@ -231,7 +219,7 @@ export class LinkableDictionary { linkPath: NodePath, ): InstructionDictionary | undefined { const linkNode = getLastNodeFromPath(linkPath); - let instructionName: CamelCaseString | undefined = undefined; + let instructionName: IdentifierString | undefined = undefined; if (isNode(linkNode, 'instructionLinkNode')) { instructionName = linkNode.identifier; } else if ('instruction' in linkNode) { diff --git a/packages/visitors-core/src/NodeSelector.ts b/packages/visitors-core/src/NodeSelector.ts index acdec4db5..63d65a411 100644 --- a/packages/visitors-core/src/NodeSelector.ts +++ b/packages/visitors-core/src/NodeSelector.ts @@ -7,10 +7,10 @@ export type NodeSelector = NodeSelectorFunction | NodeSelectorPath; /** * A string that can be used to select a node in a Codama tree. * - `*` matches any node. - * - `someText` matches the name of a node, if any. + * - `someText` matches the identifier of a node, if any. * - `[someNode]` matches a node of the given kind. * - `[someNode|someOtherNode]` matches a node with any of the given kind. - * - `[someNode]someText` matches both the kind and the name of a node. + * - `[someNode]someText` matches both the kind and the identifier of a node. * - `a.b.c` matches a node `c` such that its ancestors contains `a` and `b` in order (but not necessarily subsequent). */ export type NodeSelectorPath = string; @@ -32,12 +32,8 @@ export const getNodeSelectorFunction = (selector: NodeSelector): NodeSelectorFun return false; } - // Check names. - // NOTE: v2 identifiers are no longer camelCase-normalised, so comparing a - // camelCased selector against `node.identifier` is not quite right — the - // case-fold/underscore-strip comparison is a follow-up. Cast to `string` - // for now to keep the mechanical rename compiling without changing behaviour. - if (name && (!('identifier' in node) || camelCase(name) !== (node.identifier as string))) { + // Check identifiers. + if (name && (!('identifier' in node) || node.identifier !== name)) { return false; } diff --git a/packages/visitors-core/src/ProvidedScope.ts b/packages/visitors-core/src/ProvidedScope.ts new file mode 100644 index 000000000..41d6ce672 --- /dev/null +++ b/packages/visitors-core/src/ProvidedScope.ts @@ -0,0 +1,146 @@ +import { CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CodamaError } from '@codama/errors'; +import { + assertIsNode, + type GetNodeFromKind, + type IdentifierString, + type InjectedValueNode, + isNode, + type Node, + type NodeKind, + type ProvidedNode, +} from '@codama/nodes'; + +import { bottomUpTransformerVisitor } from './bottomUpTransformerVisitor'; +import { visit } from './visitor'; + +/** A node an injection resolved to, with the frame depth its own injections resolve against. */ +type Resolution = { depth: number; node: Node; provider?: ProvidedNode }; + +/** + * The lexical scope of `providedNode`s visible at a point of the tree, + * used to resolve `injectedValueNode`s. + * + * Each `instructionNode` carrying `provides` contributes one frame (the + * only host of `provides` today). Resolution is lexical: the innermost + * frame providing a key wins and outer frames remain visible — e.g. a + * sub-instruction may shadow a key provided by its parent. Frames are + * independent of the {@link NodeStack}, so following links (e.g. into a + * defined type) keeps the frames of the consuming instruction in scope. + * + * @see {@link recordProvidedScopeVisitor} to fill a scope during a traversal. + */ +export class ProvidedScope { + private readonly frames: (readonly ProvidedNode[])[]; + + constructor(...frames: readonly (readonly ProvidedNode[])[]) { + this.frames = frames.map(frame => [...frame]); + } + + /** Open a new innermost frame with the given providers. */ + public push(provides: readonly ProvidedNode[]): void { + this.frames.push([...provides]); + } + + /** Close the innermost frame. */ + public pop(): void { + this.frames.pop(); + } + + /** The innermost provider for the given key, if any. */ + public get(key: IdentifierString): ProvidedNode | undefined { + return this.find(key, this.frames.length)?.provider; + } + + /** + * Resolve every `injectedValueNode` within `node` — itself included — + * and return the result, which never contains an injection. + * + * An injection resolves to the innermost provided node for its key. A + * provided node is resolved against the frames outside the one + * providing it, so a provider may re-inject its own key. The + * injection's `fallback` is used when no frame provides the key, or + * when the provided chain dead-ends. Resolution is all-or-nothing: if + * any injection within `node` resolves to nothing, `undefined` is + * returned rather than a partially resolved value. A `node` containing + * no injection is returned as-is. + * + * The root result must be one of `kinds`: a provided node that is not + * throws `CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE`, any other + * node throws `CODAMA_ERROR__UNEXPECTED_NODE_KIND`. Nested + * injections are checked by the node that holds them. + */ + public resolve( + node: Node, + options: { kinds: TKind[] }, + ): Exclude, InjectedValueNode> | undefined { + const depth = this.frames.length; + const resolution = isNode(node, 'injectedValueNode') ? this.lookup(node, depth) : { depth, node }; + if (!resolution) return undefined; + if (!isNode(resolution.node, options.kinds)) { + if (resolution.provider) { + throw new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { + expectedKinds: options.kinds, + key: resolution.provider.identifier, + providedKind: resolution.node.kind, + provider: resolution.provider, + }); + } + assertIsNode(resolution.node, options.kinds); + } + const resolved = this.resolveWithin(resolution.node, resolution.depth); + return (resolved ?? undefined) as Exclude, InjectedValueNode> | undefined; + } + + public clone(): ProvidedScope { + return new ProvidedScope(...this.frames); + } + + /** Find the node an injection stands for, searching frames strictly below `depth`. */ + private lookup(node: InjectedValueNode, depth: number): Resolution | undefined { + const found = this.find(node.key, depth); + if (found) { + const { depth: providerDepth, provider } = found; + if (!isNode(provider.node, 'injectedValueNode')) { + return { depth: providerDepth, node: provider.node, provider }; + } + const chained = this.lookup(provider.node, providerDepth); + if (chained) return { ...chained, provider: chained.provider ?? provider }; + } + if (node.fallback === undefined) return undefined; + return isNode(node.fallback, 'injectedValueNode') + ? this.lookup(node.fallback, depth) + : { depth, node: node.fallback }; + } + + /** + * Replace every injection nested within `node`, resolving against frames + * strictly below `depth`. Returns `null` when any of them is unresolvable, + * and `node` itself when it contains none. + */ + private resolveWithin(node: Node, depth: number): Node | null { + let replaced = false; + let unresolvable = false; + const visitor = bottomUpTransformerVisitor([ + child => { + if (unresolvable || !isNode(child, 'injectedValueNode')) return child; + replaced = true; + const resolution = this.lookup(child, depth); + const resolved = resolution ? this.resolveWithin(resolution.node, resolution.depth) : null; + if (resolved === null) unresolvable = true; + return resolved; + }, + ]); + const result = visit(node, visitor); + if (unresolvable) return null; + return replaced ? result : node; + } + + /** Search frames strictly below `depth`, innermost first. */ + private find(key: IdentifierString, depth: number): { depth: number; provider: ProvidedNode } | undefined { + for (let index = depth - 1; index >= 0; index--) { + const provider = this.frames[index].find(p => p.identifier === key); + if (provider) return { depth: index, provider }; + } + return undefined; + } +} diff --git a/packages/visitors-core/src/applyByteSizeTransforms.ts b/packages/visitors-core/src/applyByteSizeTransforms.ts new file mode 100644 index 000000000..0c37b026f --- /dev/null +++ b/packages/visitors-core/src/applyByteSizeTransforms.ts @@ -0,0 +1,53 @@ +import { ConstantValueNode, IntegerTypeNode, Node, TransformNode, TypeNode } from '@codama/nodes'; + +import { visit, Visitor } from './visitor'; + +/** Whether a node carries a `transforms` array (i.e. is a type node). */ +export function nodeHasTransforms(node: Node): node is TypeNode { + return 'transforms' in node; +} + +const sumSizes = (values: (number | null)[]): number | null => + values.reduce((all, one) => (all === null || one === null ? null : all + one), 0 as number | null); + +/** + * The node kinds a transform's own children reach when being sized (size + * prefixes are integers; sentinels and hidden constants are constant values). + * A byte-size visitor over a wider key set is assignable to this. + */ +type TransformChildVisitor = Visitor; + +/** Apply a single transform's byte-size effect on top of the running size. */ +function applyTransform(size: number | null, transform: TransformNode, self: TransformChildVisitor): number | null { + const sizeOf = (child: ConstantValueNode | IntegerTypeNode): number | null => visit(child, self); + switch (transform.kind) { + case 'fixedSizeTransformNode': + return transform.size; + case 'sizePrefixTransformNode': + return sumSizes([size, sizeOf(transform.prefix)]); + case 'sentinelTransformNode': + return sumSizes([size, sizeOf(transform.sentinel)]); + case 'hiddenPrefixTransformNode': + return sumSizes([size, ...(transform.prefix ?? []).map(sizeOf)]); + case 'hiddenSuffixTransformNode': + return sumSizes([size, ...(transform.suffix ?? []).map(sizeOf)]); + case 'preOffsetTransformNode': + case 'postOffsetTransformNode': + return transform.strategy === 'padded' ? sumSizes([size, transform.offset]) : size; + } +} + +/** + * Fold every transform on `node` over `leafSize` (the type's size without its + * transforms), innermost-first, so each transform layers on the running size. + * `self` sizes the transforms' own children (size prefixes, sentinels, hidden + * constants). Shared by the fixed- and max-byte-size visitors, which apply + * transforms identically. + */ +export function applyByteSizeTransforms( + node: TypeNode, + leafSize: number | null, + self: TransformChildVisitor, +): number | null { + return (node.transforms ?? []).reduce((all, transform) => applyTransform(all, transform, self), leafSize); +} diff --git a/packages/visitors-core/src/getByteSizeVisitor.ts b/packages/visitors-core/src/getByteSizeVisitor.ts index 54febb9ce..59b9acc22 100644 --- a/packages/visitors-core/src/getByteSizeVisitor.ts +++ b/packages/visitors-core/src/getByteSizeVisitor.ts @@ -1,7 +1,9 @@ import { CountNode, isNode, isScalarEnum, REGISTERED_TYPE_NODE_KINDS, RegisteredTypeNode } from '@codama/nodes'; +import { applyByteSizeTransforms, nodeHasTransforms } from './applyByteSizeTransforms'; import { extendVisitor } from './extendVisitor'; import { mergeVisitor } from './generated/mergeVisitor'; +import { interceptVisitor } from './interceptVisitor'; import { LinkableDictionary } from './LinkableDictionary'; import { getLastNodeFromPath } from './NodePath'; import { NodeStack } from './NodeStack'; @@ -15,7 +17,6 @@ export type ByteSizeVisitorKeys = | 'constantValueNode' | 'definedTypeLinkNode' | 'definedTypeNode' - | 'instructionArgumentNode' | 'instructionNode'; export function getByteSizeVisitor( @@ -40,7 +41,6 @@ export function getByteSizeVisitor( 'constantValueNode', 'definedTypeLinkNode', 'definedTypeNode', - 'instructionArgumentNode', 'instructionNode', ], }, @@ -58,6 +58,10 @@ export function getByteSizeVisitor( return getArrayLikeSize(node.count, visit(node.item, self), self); }, + visitBytesType() { + return null; + }, + visitConstantValue(node, { self }) { const typeSize = visit(node.type, self); if (typeSize !== null) return typeSize; @@ -102,10 +106,6 @@ export function getByteSizeVisitor( return result; }, - visitEnumEmptyVariantType() { - return 0; - }, - visitEnumType(node, { self }) { const prefix = visit(node.size, self); if (prefix === null) return null; @@ -117,16 +117,21 @@ export function getByteSizeVisitor( : null; }, - visitFixedSizeType(node) { - return node.size; + visitEnumVariantType(node, { self }) { + return node.data ? visit(node.data, self) : 0; + }, + + visitFloatType(node) { + return node.format === 'f32' ? 4 : 8; }, visitInstruction(node, { self }) { - return sumSizes((node.arguments ?? []).map(arg => visit(arg, self))); + return node.data ? visit(node.data, self) : 0; }, - visitInstructionArgument(node, { self }) { - return visit(node.type, self); + visitIntegerType(node) { + if (node.format === 'shortU16') return null; + return parseInt(node.format.slice(1), 10) / 8; }, visitMapType(node, { self }) { @@ -134,26 +139,11 @@ export function getByteSizeVisitor( return getArrayLikeSize(node.count, innerSize, self); }, - visitNumberType(node) { - if (node.format === 'shortU16') return null; - return parseInt(node.format.slice(1), 10) / 8; - }, - visitOptionType(node, { self }) { if (!node.fixed) return null; return sumSizes([visit(node.prefix, self), visit(node.item, self)]); }, - visitPostOffsetType(node, { self }) { - const typeSize = visit(node.type, self); - return node.strategy === 'padded' ? sumSizes([typeSize, node.offset]) : typeSize; - }, - - visitPreOffsetType(node, { self }) { - const typeSize = visit(node.type, self); - return node.strategy === 'padded' ? sumSizes([typeSize, node.offset]) : typeSize; - }, - visitPublicKeyType() { return 32; }, @@ -168,8 +158,6 @@ export function getByteSizeVisitor( }, visitStringType() { - // Strings have no fixed byte size; size is determined by an enclosing - // wrapper such as `sizePrefixTypeNode` or `fixedSizeTypeNode`. return null; }, @@ -180,6 +168,17 @@ export function getByteSizeVisitor( return zeroSize === itemSize ? itemSize : null; }, }), + // Layer each type node's flat `transforms` on top of its own size. + // The base merge visitor ignores transform children (their kinds + // aren't in the visitor keys), so `next` yields the untransformed + // size; `self` sizes the transforms' own children. + v => + interceptVisitor(v, (node, next, self) => { + if (!nodeHasTransforms(node) || (node.transforms ?? []).length === 0) { + return next(node); + } + return applyByteSizeTransforms(node, next(node), self); + }), v => recordNodeStackVisitor(v, stack), ); } diff --git a/packages/visitors-core/src/getDebugStringVisitor.ts b/packages/visitors-core/src/getDebugStringVisitor.ts index ac08c8d35..53f93bc0a 100644 --- a/packages/visitors-core/src/getDebugStringVisitor.ts +++ b/packages/visitors-core/src/getDebugStringVisitor.ts @@ -1,4 +1,4 @@ -import { Node } from '@codama/nodes'; +import { getTextNodeContent, Node } from '@codama/nodes'; import { mergeVisitor } from './generated/mergeVisitor'; import { interceptVisitor } from './interceptVisitor'; @@ -62,7 +62,7 @@ function getNodeDetails(node: Node): string[] { case 'instructionByteDeltaNode': return [...(node.subtract ? ['subtract'] : []), ...(node.withHeader ? ['withHeader'] : [])]; case 'instructionStatusNode': - return [node.lifecycle, ...(node.message ? [node.message] : [])]; + return [node.lifecycle, ...(node.message ? [getTextNodeContent(node.message)] : [])]; case 'errorNode': return [node.code.toString(), node.identifier]; case 'constantNode': @@ -70,23 +70,33 @@ function getNodeDetails(node: Node): string[] { case 'accountLinkNode': case 'definedTypeLinkNode': case 'instructionAccountLinkNode': - case 'instructionArgumentLinkNode': case 'instructionLinkNode': case 'pdaLinkNode': case 'programLinkNode': return [node.identifier]; - case 'numberTypeNode': - return [node.format, ...(node.endian === 'be' ? ['bigEndian'] : [])]; - case 'amountTypeNode': - return [node.decimals.toString(), ...(node.unit ? [node.unit] : [])]; + case 'integerTypeNode': + case 'floatTypeNode': + return [node.format, ...(node.endian === 'be' ? ['bigEndian'] : []), ...(node.unit ? [node.unit] : [])]; + case 'fixedPointTypeNode': + return [ + `scale:${node.scale}`, + ...(node.base !== undefined ? [`base:${node.base}`] : []), + ...(node.unit ? [node.unit] : []), + ]; + case 'durationTypeNode': + case 'dateTimeTypeNode': + return node.ticksPerSecond !== undefined ? [`ticksPerSecond:${node.ticksPerSecond}`] : []; case 'stringTypeNode': return [node.encoding]; case 'optionTypeNode': return node.fixed ? ['fixed'] : []; case 'fixedCountNode': return [node.value.toString()]; - case 'numberValueNode': - return [node.number.toString()]; + case 'sentinelCountNode': + return node.strategy ? [node.strategy] : []; + case 'integerValueNode': + case 'floatValueNode': + return [node.value]; case 'stringValueNode': return [node.string]; case 'booleanValueNode': @@ -97,20 +107,35 @@ function getNodeDetails(node: Node): string[] { return [...(node.identifier ? [`${node.identifier}`] : []), node.publicKey]; case 'enumValueNode': return [node.variant]; - case 'resolverValueNode': - return [node.name]; + case 'textNode': + return [getTextNodeContent(node)]; + case 'dataValueNode': + return [node.path]; + case 'accountDataValueNode': + return [node.account, ...(node.path ? [node.path] : [])]; + case 'injectedValueNode': + return [node.key]; + case 'enumVariantTypeNode': + return [ + node.identifier, + ...(node.discriminator !== undefined ? [`discriminator:${node.discriminator}`] : []), + ]; + case 'pluginNode': + return [node.namespace]; case 'constantDiscriminatorNode': return node.offset > 0 ? [`offset:${node.offset}`] : []; case 'fieldDiscriminatorNode': - return [node.name, ...(node.offset > 0 ? [`offset:${node.offset}`] : [])]; + return [node.path, ...(node.offset > 0 ? [`offset:${node.offset}`] : [])]; case 'sizeDiscriminatorNode': return [node.size.toString()]; - case 'fixedSizeTypeNode': + case 'fixedSizeTransformNode': return [node.size.toString()]; - case 'preOffsetTypeNode': - return [node.offset.toString(), node.strategy ?? 'relative']; - case 'postOffsetTypeNode': - return [node.offset.toString(), node.strategy ?? 'relative']; + case 'sizePrefixTransformNode': + case 'sentinelTransformNode': + return []; + case 'preOffsetTransformNode': + case 'postOffsetTransformNode': + return [node.offset.toString(), node.strategy]; default: return 'identifier' in node ? [node.identifier] : []; } diff --git a/packages/visitors-core/src/getMaxByteSizeVisitor.ts b/packages/visitors-core/src/getMaxByteSizeVisitor.ts index 1d01c1369..424a82a22 100644 --- a/packages/visitors-core/src/getMaxByteSizeVisitor.ts +++ b/packages/visitors-core/src/getMaxByteSizeVisitor.ts @@ -1,8 +1,10 @@ import { CountNode, isNode, isScalarEnum, REGISTERED_TYPE_NODE_KINDS } from '@codama/nodes'; +import { applyByteSizeTransforms, nodeHasTransforms } from './applyByteSizeTransforms'; import { extendVisitor } from './extendVisitor'; import { mergeVisitor } from './generated/mergeVisitor'; import { ByteSizeVisitorKeys } from './getByteSizeVisitor'; +import { interceptVisitor } from './interceptVisitor'; import { LinkableDictionary } from './LinkableDictionary'; import { getLastNodeFromPath } from './NodePath'; import { NodeStack } from './NodeStack'; @@ -32,7 +34,6 @@ export function getMaxByteSizeVisitor( 'constantValueNode', 'definedTypeLinkNode', 'definedTypeNode', - 'instructionArgumentNode', 'instructionNode', ], }, @@ -50,6 +51,10 @@ export function getMaxByteSizeVisitor( return getArrayLikeSize(node.count, visit(node.item, self), self); }, + visitBytesType() { + return null; + }, + visitConstantValue(node, { self }) { const typeSize = visit(node.type, self); if (typeSize !== null) return typeSize; @@ -94,10 +99,6 @@ export function getMaxByteSizeVisitor( return result; }, - visitEnumEmptyVariantType() { - return 0; - }, - visitEnumType(node, { self }) { const prefix = visit(node.size, self); if (prefix === null) return null; @@ -108,16 +109,21 @@ export function getMaxByteSizeVisitor( return prefix + maxVariantSize; }, - visitFixedSizeType(node) { - return node.size; + visitEnumVariantType(node, { self }) { + return node.data ? visit(node.data, self) : 0; + }, + + visitFloatType(node) { + return node.format === 'f32' ? 4 : 8; }, visitInstruction(node, { self }) { - return sumSizes((node.arguments ?? []).map(arg => visit(arg, self))); + return node.data ? visit(node.data, self) : 0; }, - visitInstructionArgument(node, { self }) { - return visit(node.type, self); + visitIntegerType(node) { + if (node.format === 'shortU16') return 3; + return parseInt(node.format.slice(1), 10) / 8; }, visitMapType(node, { self }) { @@ -125,25 +131,10 @@ export function getMaxByteSizeVisitor( return getArrayLikeSize(node.count, innerSize, self); }, - visitNumberType(node) { - if (node.format === 'shortU16') return 3; - return parseInt(node.format.slice(1), 10) / 8; - }, - visitOptionType(node, { self }) { return sumSizes([visit(node.prefix, self), visit(node.item, self)]); }, - visitPostOffsetType(node, { self }) { - const typeSize = visit(node.type, self); - return node.strategy === 'padded' ? sumSizes([typeSize, node.offset]) : typeSize; - }, - - visitPreOffsetType(node, { self }) { - const typeSize = visit(node.type, self); - return node.strategy === 'padded' ? sumSizes([typeSize, node.offset]) : typeSize; - }, - visitPublicKeyType() { return 32; }, @@ -158,8 +149,6 @@ export function getMaxByteSizeVisitor( }, visitStringType() { - // Strings have no fixed maximum byte size; size is determined by an - // enclosing wrapper such as `sizePrefixTypeNode` or `fixedSizeTypeNode`. return null; }, @@ -171,6 +160,15 @@ export function getMaxByteSizeVisitor( return Math.max(itemSize, zeroSize); }, }), + // Layer each type node's flat `transforms` on top of its own size + // (see `getByteSizeVisitor` for the rationale). + v => + interceptVisitor(v, (node, next, self) => { + if (!nodeHasTransforms(node) || (node.transforms ?? []).length === 0) { + return next(node); + } + return applyByteSizeTransforms(node, next(node), self); + }), v => recordNodeStackVisitor(v, stack), ); } diff --git a/packages/visitors-core/src/getResolvedInstructionInputsVisitor.ts b/packages/visitors-core/src/getResolvedInstructionInputsVisitor.ts index 47b91f223..2839bf000 100644 --- a/packages/visitors-core/src/getResolvedInstructionInputsVisitor.ts +++ b/packages/visitors-core/src/getResolvedInstructionInputsVisitor.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-case-declarations */ import { CODAMA_ERROR__VISITORS__CANNOT_USE_OPTIONAL_ACCOUNT_AS_PDA_SEED_VALUE, CODAMA_ERROR__VISITORS__CYCLIC_DEPENDENCY_DETECTED_WHEN_RESOLVING_INSTRUCTION_DEFAULT_VALUES, @@ -8,62 +7,147 @@ import { import { AccountValueNode, accountValueNode, - ArgumentValueNode, - argumentValueNode, - CamelCaseString, - getAllInstructionArguments, + DataValueNode, + dataValueNode, + IdentifierString, InstructionAccountNode, - InstructionArgumentNode, InstructionInputValueNode, + INSTRUCTION_INPUT_VALUE_NODES, InstructionNode, isNode, + PathString, PdaSeedValueNode, + StructFieldTypeNode, VALUE_NODES, } from '@codama/nodes'; +import { LinkableDictionary } from './LinkableDictionary'; +import { getLastNodeFromPath } from './NodePath'; +import { NodeStack } from './NodeStack'; +import { pipe } from './pipe'; +import { ProvidedScope } from './ProvidedScope'; +import { recordNodeStackVisitor } from './recordNodeStackVisitor'; +import { recordProvidedScopeVisitor } from './recordProvidedScopeVisitor'; import { singleNodeVisitor } from './singleNodeVisitor'; import { Visitor } from './visitor'; -export type ResolvedInstructionInput = ResolvedInstructionAccount | ResolvedInstructionArgument; -export type ResolvedInstructionAccount = InstructionAccountNode & { +/** A resolved account or data-field input, discriminated by `node.kind`. */ +export type ResolvedInstructionInput = ResolvedInstructionAccount | ResolvedInstructionDataField; + +export type ResolvedInstructionAccount = { dependsOn: InstructionDependency[]; isPda: boolean; + node: InstructionAccountNode; + /** The input's default value after resolving any `injectedValueNode` through the {@link ProvidedScope}. */ + resolvedDefaultValue?: InstructionInputValueNode; resolvedIsOptional: boolean; resolvedIsSigner: boolean | 'either'; }; -export type ResolvedInstructionArgument = InstructionArgumentNode & { + +export type ResolvedInstructionDataField = { dependsOn: InstructionDependency[]; + node: StructFieldTypeNode; + /** The field's full path within `instructionNode.data`, e.g. `config.bump`. */ + path: PathString; + resolvedDefaultValue?: InstructionInputValueNode; }; -type InstructionInput = InstructionAccountNode | InstructionArgumentNode; -type InstructionDependency = AccountValueNode | ArgumentValueNode; +export type InstructionDependency = AccountValueNode | DataValueNode; + +/** An input in the dependency graph — an account, or a data field addressed by its path. */ +type InstructionInput = AccountInput | DataInput; +type AccountInput = { key: IdentifierString; kind: 'account'; node: InstructionAccountNode }; +type DataInput = { key: PathString; kind: 'data'; node: StructFieldTypeNode }; + +/** + * Resolve the default values of an instruction's accounts and data fields, + * returning them in dependency order. + * + * @param linkables - Used to follow a `definedTypeLinkNode` in the instruction's `data`. + * @param options.stack - The ancestry of the visited instruction. It must contain the + * instruction's `programNode` for `definedTypeLinkNode` data to be followed; otherwise a + * linked `data` type contributes no fields. + * @param options.scope - The providers enclosing the visited instruction (e.g. its parent + * instructions). The instruction's own `provides` are added on top for the visit. + * @param options.includeDataValueNodes - Whether to include data fields whose resolved + * default is a static value. + */ export function getResolvedInstructionInputsVisitor( - options: { includeDataArgumentValueNodes?: boolean } = {}, + linkables: LinkableDictionary, + options: { includeDataValueNodes?: boolean; scope?: ProvidedScope; stack?: NodeStack } = {}, ): Visitor { - const includeDataArgumentValueNodes = options.includeDataArgumentValueNodes ?? false; - let stack: InstructionInput[] = []; + const includeDataValueNodes = options.includeDataValueNodes ?? false; + const stack = options.stack ?? new NodeStack(); + const scope = options.scope ?? new ProvidedScope(); + + // Per-visit state, reset at the start of every `instructionNode` visit. + let dfsStack: InstructionInput[] = []; let resolved: ResolvedInstructionInput[] = []; - let visitedAccounts = new Map(); - let visitedArgs = new Map(); + let visitedAccounts = new Map(); + let visitedData = new Map(); + let dataFields: DataInput[] = []; + let dataDefaults = new Map(); + let bumpAccounts = new Set(); + + // Resolves every injection within the default value, including nested + // ones (e.g. a PDA seed). A missing provider is not an error: a reusable + // data shape may offer an optional hook that this instruction does not fill. + function resolveDefaultValue( + defaultValue: InstructionInputValueNode | undefined, + ): InstructionInputValueNode | undefined { + if (defaultValue === undefined) return undefined; + return scope.resolve(defaultValue, { kinds: INSTRUCTION_INPUT_VALUE_NODES }); + } + + // Walk `instructionNode.data`, following defined-type links, and yield + // every struct field with its full path. Fields are only addressable + // where the data type resolves to a struct. + function collectDataFields(instruction: InstructionNode): DataInput[] { + const fields: DataInput[] = []; + const walkedDefinedTypes = new Set(); + + const walk = (type: InstructionNode['data'], prefix: string): void => { + if (!type) return; + if (isNode(type, 'definedTypeLinkNode')) { + const linkedPath = linkables.getPath([...stack.getPath(), type]); + if (!linkedPath) return; + const definedType = getLastNodeFromPath(linkedPath); + if (walkedDefinedTypes.has(definedType.identifier)) return; + walkedDefinedTypes.add(definedType.identifier); + stack.pushPath(linkedPath); + walk(definedType.type, prefix); + stack.popPath(); + return; + } + if (!isNode(type, 'structTypeNode')) return; + (type.fields ?? []).forEach(field => { + const path = (prefix ? `${prefix}.${field.identifier}` : field.identifier) as PathString; + fields.push({ key: path, kind: 'data', node: field }); + if (isNode(field.type, 'structTypeNode') || isNode(field.type, 'definedTypeLinkNode')) { + walk(field.type, path); + } + }); + }; + + walk(instruction.data, ''); + return fields; + } function resolveInstructionInput(instruction: InstructionNode, input: InstructionInput): void { // Ensure we don't visit the same input twice. - if ( - (isNode(input, 'instructionAccountNode') && visitedAccounts.has(input.identifier)) || - (isNode(input, 'instructionArgumentNode') && visitedArgs.has(input.identifier)) - ) { + if (input.kind === 'account' ? visitedAccounts.has(input.key) : visitedData.has(input.key)) { return; } // Ensure we don't have a circular dependency. - const isCircular = stack.some(({ kind, identifier }) => kind === input.kind && identifier === input.identifier); + const isCircular = dfsStack.some(entry => entry.kind === input.kind && entry.key === input.key); if (isCircular) { - const cycle = [...stack, input]; + const cycle = [...dfsStack, input]; throw new CodamaError( CODAMA_ERROR__VISITORS__CYCLIC_DEPENDENCY_DETECTED_WHEN_RESOLVING_INSTRUCTION_DEFAULT_VALUES, { - cycle, - formattedCycle: cycle.map(({ identifier }) => identifier).join(' -> '), + cycle: cycle.map(entry => entry.node), + formattedCycle: cycle.map(entry => entry.key).join(' -> '), instruction, instructionName: instruction.identifier, }, @@ -71,67 +155,64 @@ export function getResolvedInstructionInputsVisitor( } // Resolve whilst keeping track of the stack. - stack.push(input); + dfsStack.push(input); const localResolved = - input.kind === 'instructionAccountNode' + input.kind === 'account' ? resolveInstructionAccount(instruction, input) - : resolveInstructionArgument(instruction, input); - stack.pop(); + : resolveInstructionDataField(instruction, input); + dfsStack.pop(); // Store the resolved input. resolved.push(localResolved); - if (localResolved.kind === 'instructionAccountNode') { - visitedAccounts.set(input.identifier, localResolved); + if (localResolved.node.kind === 'instructionAccountNode') { + visitedAccounts.set(input.key as IdentifierString, localResolved as ResolvedInstructionAccount); } else { - visitedArgs.set(input.identifier, localResolved); + visitedData.set(input.key as PathString, localResolved as ResolvedInstructionDataField); } } - function resolveInstructionAccount( - instruction: InstructionNode, - account: InstructionAccountNode, - ): ResolvedInstructionAccount { + function resolveInstructionAccount(instruction: InstructionNode, input: AccountInput): ResolvedInstructionAccount { + const account = input.node; + const resolvedDefaultValue = resolveDefaultValue(account.defaultValue); + // Find and visit dependencies first. - const dependsOn = getInstructionDependencies(account); - resolveInstructionDependencies(instruction, account, dependsOn); + const dependsOn = getDependencies(resolvedDefaultValue); + resolveDependencies(instruction, input, dependsOn); const localResolved: ResolvedInstructionAccount = { - ...account, dependsOn, - isPda: getAllInstructionArguments(instruction).some( - argument => - isNode(argument.defaultValue, 'accountBumpValueNode') && - argument.defaultValue.identifier === account.identifier, - ), + isPda: bumpAccounts.has(account.identifier), + node: account, + ...(resolvedDefaultValue !== undefined && { resolvedDefaultValue }), resolvedIsOptional: !!account.isOptional, resolvedIsSigner: account.isSigner, }; - switch (localResolved.defaultValue?.kind) { - case 'accountValueNode': - const defaultAccount = visitedAccounts.get(localResolved.defaultValue.identifier)!; - const resolvedIsPublicKey = account.isSigner === false && defaultAccount.isSigner === false; - const resolvedIsSigner = account.isSigner === true && defaultAccount.isSigner === true; + switch (resolvedDefaultValue?.kind) { + case 'accountValueNode': { + const defaultAccount = visitedAccounts.get(resolvedDefaultValue.identifier)!; + const resolvedIsPublicKey = account.isSigner === false && defaultAccount.node.isSigner === false; + const resolvedIsSigner = account.isSigner === true && defaultAccount.node.isSigner === true; const resolvedIsOptionalSigner = !resolvedIsPublicKey && !resolvedIsSigner; localResolved.resolvedIsSigner = resolvedIsOptionalSigner ? 'either' : resolvedIsSigner; - localResolved.resolvedIsOptional = !!defaultAccount.isOptional; + localResolved.resolvedIsOptional = !!defaultAccount.node.isOptional; break; + } case 'publicKeyValueNode': case 'programLinkNode': case 'programIdValueNode': localResolved.resolvedIsSigner = account.isSigner === false ? false : 'either'; localResolved.resolvedIsOptional = false; break; - case 'pdaValueNode': + case 'pdaValueNode': { localResolved.resolvedIsSigner = account.isSigner === false ? false : 'either'; localResolved.resolvedIsOptional = false; - const { seeds } = localResolved.defaultValue; - (seeds ?? []).forEach(seed => { + (resolvedDefaultValue.seeds ?? []).forEach(seed => { if (!isNode(seed.value, 'accountValueNode')) return; const dependency = visitedAccounts.get(seed.value.identifier)!; if (dependency.resolvedIsOptional) { throw new CodamaError(CODAMA_ERROR__VISITORS__CANNOT_USE_OPTIONAL_ACCOUNT_AS_PDA_SEED_VALUE, { - instruction: instruction, + instruction, instructionAccount: account, instructionAccountName: account.identifier, instructionName: instruction.identifier, @@ -143,9 +224,9 @@ export function getResolvedInstructionInputsVisitor( } }); break; + } case 'identityValueNode': case 'payerValueNode': - case 'resolverValueNode': localResolved.resolvedIsOptional = false; break; default: @@ -155,18 +236,19 @@ export function getResolvedInstructionInputsVisitor( return localResolved; } - function resolveInstructionArgument( - instruction: InstructionNode, - argument: InstructionArgumentNode, - ): ResolvedInstructionArgument { - // Find and visit dependencies first. - const dependsOn = getInstructionDependencies(argument); - resolveInstructionDependencies(instruction, argument, dependsOn); - - return { ...argument, dependsOn }; + function resolveInstructionDataField(instruction: InstructionNode, input: DataInput): ResolvedInstructionDataField { + const resolvedDefaultValue = dataDefaults.get(input.key); + const dependsOn = getDependencies(resolvedDefaultValue); + resolveDependencies(instruction, input, dependsOn); + return { + dependsOn, + node: input.node, + path: input.key, + ...(resolvedDefaultValue !== undefined && { resolvedDefaultValue }), + }; } - function resolveInstructionDependencies( + function resolveDependencies( instruction: InstructionNode, parent: InstructionInput, dependencies: InstructionDependency[], @@ -178,35 +260,15 @@ export function getResolvedInstructionInputsVisitor( a => a.identifier === dependency.identifier, ); if (!dependencyAccount) { - throw new CodamaError(CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY, { - dependency, - dependencyKind: dependency.kind, - dependencyName: dependency.identifier, - instruction, - instructionName: instruction.identifier, - parent, - parentKind: parent.kind, - parentName: parent.identifier, - }); + throwInvalidDependency(instruction, parent, dependency, dependency.identifier); } - input = { ...dependencyAccount }; - } else if (isNode(dependency, 'argumentValueNode')) { - const dependencyArgument = getAllInstructionArguments(instruction).find( - a => a.identifier === dependency.name, - ); - if (!dependencyArgument) { - throw new CodamaError(CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY, { - dependency, - dependencyKind: dependency.kind, - dependencyName: dependency.name, - instruction, - instructionName: instruction.identifier, - parent, - parentKind: parent.kind, - parentName: parent.identifier, - }); + input = { key: dependencyAccount.identifier, kind: 'account', node: dependencyAccount }; + } else if (isNode(dependency, 'dataValueNode')) { + const dependencyField = findFieldByPath(dataFields, dependency.path); + if (!dependencyField) { + throwInvalidDependency(instruction, parent, dependency, dependency.path); } - input = { ...dependencyArgument }; + input = dependencyField; } if (input) { resolveInstructionInput(instruction, input); @@ -214,90 +276,146 @@ export function getResolvedInstructionInputsVisitor( }); } - return singleNodeVisitor('instructionNode', (node): ResolvedInstructionInput[] => { + function throwInvalidDependency( + instruction: InstructionNode, + parent: InstructionInput, + dependency: InstructionDependency, + dependencyName: IdentifierString | PathString, + ): never { + throw new CodamaError(CODAMA_ERROR__VISITORS__INVALID_INSTRUCTION_DEFAULT_VALUE_DEPENDENCY, { + dependency, + dependencyKind: dependency.kind, + dependencyName, + instruction, + instructionName: instruction.identifier, + parent: parent.node, + parentKind: parent.node.kind, + parentName: parent.key, + }); + } + + const visitor = singleNodeVisitor('instructionNode', (node): ResolvedInstructionInput[] => { // Ensure we always start with a clean slate. - stack = []; + dfsStack = []; resolved = []; visitedAccounts = new Map(); - visitedArgs = new Map(); + visitedData = new Map(); + + // The data fields and their resolved defaults are fixed for the + // duration of the visit, so compute them once. + dataFields = collectDataFields(node); + dataDefaults = new Map( + dataFields.map(field => [field.key, resolveDefaultValue(asInstructionInputValue(field.node.defaultValue))]), + ); + bumpAccounts = new Set( + [...dataDefaults.values()].flatMap(value => + isNode(value, 'accountBumpValueNode') ? [value.identifier] : [], + ), + ); + + const dataInputs = dataFields.filter(field => { + const value = dataDefaults.get(field.key); + if (!value) return false; + // Skip static value defaults unless explicitly requested — there's + // nothing to resolve for a plain literal. + return includeDataValueNodes || !isNode(value, VALUE_NODES); + }); const inputs: InstructionInput[] = [ - ...(node.accounts ?? []), - ...(node.arguments ?? []).filter(a => { - if (includeDataArgumentValueNodes) return a.defaultValue; - return a.defaultValue && !isNode(a.defaultValue, VALUE_NODES); - }), - ...(node.extraArguments ?? []).filter(a => a.defaultValue), + ...(node.accounts ?? []).map(account => ({ + key: account.identifier, + kind: 'account' as const, + node: account, + })), + ...dataInputs, ]; - // Visit all instruction accounts. - inputs.forEach(input => { - resolveInstructionInput(node, input); - }); + inputs.forEach(input => resolveInstructionInput(node, input)); return resolved; }); + + return pipe( + visitor, + // Opens the visited instruction's own `provides` frame on top of any + // frames the caller's scope already holds (e.g. parent instructions). + v => recordProvidedScopeVisitor(v, scope), + v => recordNodeStackVisitor(v, stack), + ); +} + +/** + * Match a `dataValueNode` path to a data field. A path deeper than the + * struct-field graph (e.g. into an array element like `config.fees[0]`) + * resolves to the longest field-path prefix that exists — the field that + * holds the referenced element. + */ +function findFieldByPath(fields: DataInput[], path: PathString): DataInput | undefined { + return fields + .filter(f => path === f.key || path.startsWith(`${f.key}.`) || path.startsWith(`${f.key}[`)) + .sort((a, b) => b.key.length - a.key.length)[0]; +} + +/** + * A `structFieldTypeNode.defaultValue` is a `ValueNode`, a strict subset of + * `InstructionInputValueNode`. This narrows it for the shared resolution + * helpers. + */ +function asInstructionInputValue( + value: InstructionInputValueNode | StructFieldTypeNode['defaultValue'], +): InstructionInputValueNode | undefined { + return value as InstructionInputValueNode | undefined; } export function deduplicateInstructionDependencies(dependencies: InstructionDependency[]): InstructionDependency[] { - const accounts = new Map(); - const args = new Map(); + const accounts = new Map(); + const data = new Map(); dependencies.forEach(dependency => { if (isNode(dependency, 'accountValueNode')) { accounts.set(dependency.identifier, dependency); - } else if (isNode(dependency, 'argumentValueNode')) { - args.set(dependency.name, dependency); + } else if (isNode(dependency, 'dataValueNode')) { + data.set(dependency.path, dependency); } }); - return [...accounts.values(), ...args.values()]; + return [...accounts.values(), ...data.values()]; } -export function getInstructionDependencies(input: InstructionInput | InstructionNode): InstructionDependency[] { - if (isNode(input, 'instructionNode')) { - return deduplicateInstructionDependencies([ - ...(input.accounts ?? []).flatMap(getInstructionDependencies), - ...(input.arguments ?? []).flatMap(getInstructionDependencies), - ...(input.extraArguments ?? []).flatMap(getInstructionDependencies), - ]); - } - - if (!input.defaultValue) return []; +/** The account/data references a default value depends on, resolved recursively. */ +export function getDependencies(defaultValue: InstructionInputValueNode | undefined): InstructionDependency[] { + if (!defaultValue) return []; - const getNestedDependencies = (defaultValue: InstructionInputValueNode | undefined): InstructionDependency[] => { - if (!defaultValue) return []; - return getInstructionDependencies({ ...input, defaultValue }); - }; + if (isNode(defaultValue, ['accountValueNode', 'accountBumpValueNode'])) { + return [accountValueNode(defaultValue.identifier)]; + } - if (isNode(input.defaultValue, ['accountValueNode', 'accountBumpValueNode'])) { - return [accountValueNode(input.defaultValue.identifier)]; + if (isNode(defaultValue, 'accountDataValueNode')) { + return [accountValueNode(defaultValue.account)]; } - if (isNode(input.defaultValue, ['argumentValueNode'])) { - return [argumentValueNode(input.defaultValue.name)]; + if (isNode(defaultValue, 'dataValueNode')) { + return [dataValueNode(defaultValue.path)]; } - if (isNode(input.defaultValue, 'pdaValueNode')) { - const dependencies = new Map(); - (input.defaultValue.seeds ?? []).forEach(seed => { - if (isNode(seed.value, ['accountValueNode', 'argumentValueNode'])) { - dependencies.set(seed.value.name, { ...seed.value }); + if (isNode(defaultValue, 'pdaValueNode')) { + const dependencies: InstructionDependency[] = []; + (defaultValue.seeds ?? []).forEach(seed => { + if (isNode(seed.value, 'accountValueNode') || isNode(seed.value, 'dataValueNode')) { + dependencies.push({ ...seed.value }); } }); - return [ - ...dependencies.values(), - ...(input.defaultValue.programId ? ([input.defaultValue.programId] as const) : []), - ]; - } - - if (isNode(input.defaultValue, 'resolverValueNode')) { - return input.defaultValue.dependsOn ?? []; + return deduplicateInstructionDependencies([ + ...dependencies, + ...(defaultValue.programId && isNode(defaultValue.programId, 'accountValueNode') + ? [defaultValue.programId] + : []), + ]); } - if (isNode(input.defaultValue, 'conditionalValueNode')) { + if (isNode(defaultValue, 'conditionalValueNode')) { return deduplicateInstructionDependencies([ - ...getNestedDependencies(input.defaultValue.condition), - ...getNestedDependencies(input.defaultValue.ifTrue), - ...getNestedDependencies(input.defaultValue.ifFalse), + ...getDependencies(defaultValue.condition), + ...getDependencies(defaultValue.ifTrue), + ...getDependencies(defaultValue.ifFalse), ]); } diff --git a/packages/visitors-core/src/identityVisitor.ts b/packages/visitors-core/src/identityVisitor.ts index 008aad2a7..2e0127556 100644 --- a/packages/visitors-core/src/identityVisitor.ts +++ b/packages/visitors-core/src/identityVisitor.ts @@ -1,21 +1,8 @@ -import { - assertIsNode, - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - enumTupleVariantTypeNode, - hiddenPrefixTypeNode, - hiddenSuffixTypeNode, - type Node, - type NodeKind, - REGISTERED_NODE_KINDS, - removeNullAndAssertIsNodeFilter, - resolverValueNode, - TYPE_NODES, -} from '@codama/nodes'; +import { type Node, type NodeKind, REGISTERED_NODE_KINDS } from '@codama/nodes'; import { extendVisitor, type VisitorOverrides } from './extendVisitor'; import { identityVisitor as identityVisitorCore } from './generated/identityVisitor'; -import { visit as baseVisit, type Visitor } from './visitor'; +import { type Visitor } from './visitor'; /** * Identity visitor: rebuilds the tree node-by-node so callers can @@ -25,19 +12,11 @@ import { visit as baseVisit, type Visitor } from './visitor'; * * The mechanical walk lives in `./generated/identityVisitor` (one * branch per spec node, derived from the attribute structure of - * `@codama/spec`). This wrapper layers a handful of *semantic* - * overrides on top — transformations that aren't derivable from the - * spec alone: + * `@codama/spec`). This wrapper layers the *semantic* overrides that + * aren't derivable from the spec alone: * - * - `enumStructVariantTypeNode` / `enumTupleVariantTypeNode`: - * downgrade to `enumEmptyVariantTypeNode` when the payload is - * empty (no fields / no items). - * - `hiddenPrefixTypeNode` / `hiddenSuffixTypeNode`: drop the - * wrapper when the prefix/suffix array is empty. * - `conditionalValueNode`: return `null` when both `ifTrue` and * `ifFalse` are absent post-walk. - * - `resolverValueNode`: collapse an empty `dependsOn` array back - * to `undefined` so equality checks remain stable. */ export function identityVisitor( options: { keys?: TNodeKind[] } = {}, @@ -46,77 +25,13 @@ export function identityVisitor( const base = identityVisitorCore(options); // Build overrides against the broad `NodeKind` shape; the cast // back to the narrowed visitor happens at `extendVisitor`'s - // return. This mirrors the pattern the hand-written visitor used - // for years: every override is type-checked against the full Node + // return. Every override is type-checked against the full Node // union, then `extendVisitor` ignores any override whose kind // isn't actually in `keys` at runtime. const overrides: VisitorOverrides = {}; - const visit = - (v: Visitor) => - (node: Node): Node | null => - keys.includes(node.kind) ? baseVisit(node, v) : Object.freeze({ ...node }); - - if (keys.includes('enumStructVariantTypeNode' as TNodeKind)) { - overrides.visitEnumStructVariantType = function visitEnumStructVariantType(node, { self }) { - const newStruct = visit(self)(node.struct); - if (!newStruct) { - return enumEmptyVariantTypeNode(node.identifier); - } - assertIsNode(newStruct, 'structTypeNode'); - if ((newStruct.fields ?? []).length === 0) { - return enumEmptyVariantTypeNode(node.identifier); - } - return enumStructVariantTypeNode(node.identifier, newStruct, node.discriminator); - }; - } - - if (keys.includes('enumTupleVariantTypeNode' as TNodeKind)) { - overrides.visitEnumTupleVariantType = function visitEnumTupleVariantType(node, { self }) { - const newTuple = visit(self)(node.tuple); - if (!newTuple) { - return enumEmptyVariantTypeNode(node.identifier); - } - assertIsNode(newTuple, 'tupleTypeNode'); - if ((newTuple.items ?? []).length === 0) { - return enumEmptyVariantTypeNode(node.identifier); - } - return enumTupleVariantTypeNode(node.identifier, newTuple, node.discriminator); - }; - } - - if (keys.includes('hiddenPrefixTypeNode' as TNodeKind)) { - overrides.visitHiddenPrefixType = function visitHiddenPrefixType(node, { self }) { - const type = visit(self)(node.type); - if (type === null) return null; - assertIsNode(type, TYPE_NODES); - const prefix = (node.prefix ?? []) - .map(visit(self)) - .filter(removeNullAndAssertIsNodeFilter('constantValueNode')); - if (prefix.length === 0) return type; - return hiddenPrefixTypeNode(type, prefix); - }; - } - - if (keys.includes('hiddenSuffixTypeNode' as TNodeKind)) { - overrides.visitHiddenSuffixType = function visitHiddenSuffixType(node, { self }) { - const type = visit(self)(node.type); - if (type === null) return null; - assertIsNode(type, TYPE_NODES); - const suffix = (node.suffix ?? []) - .map(visit(self)) - .filter(removeNullAndAssertIsNodeFilter('constantValueNode')); - if (suffix.length === 0) return type; - return hiddenSuffixTypeNode(type, suffix); - }; - } if (keys.includes('conditionalValueNode' as TNodeKind)) { overrides.visitConditionalValue = function visitConditionalValue(node, { next }) { - // Walk via the generated branch first, then enforce the - // "both arms absent → drop the node" rule. The generated - // visitor preserves all visited attrs, so we can inspect - // the result post-walk rather than re-implementing the - // walk here. const visited = next(node); if (visited === null || visited.kind !== 'conditionalValueNode') return visited; if (visited.ifTrue === undefined && visited.ifFalse === undefined) return null; @@ -124,18 +39,6 @@ export function identityVisitor( }; } - if (keys.includes('resolverValueNode' as TNodeKind)) { - overrides.visitResolverValue = function visitResolverValue(node, { self }) { - const dependsOn = (node.dependsOn ?? []) - .map(visit(self)) - .filter(removeNullAndAssertIsNodeFilter(['accountValueNode', 'argumentValueNode'])); - return resolverValueNode(node.name, { - ...node, - dependsOn: dependsOn.length === 0 ? undefined : dependsOn, - }); - }; - } - return extendVisitor(base as unknown as Visitor, overrides) as unknown as Visitor< Node | null, TNodeKind diff --git a/packages/visitors-core/src/index.ts b/packages/visitors-core/src/index.ts index 6e81e7145..a3d238237 100644 --- a/packages/visitors-core/src/index.ts +++ b/packages/visitors-core/src/index.ts @@ -22,8 +22,10 @@ export * from './NodeSelector'; export * from './NodeStack'; export * from './nonNullableIdentityVisitor'; export * from './pipe'; +export * from './ProvidedScope'; export * from './recordLinkablesVisitor'; export * from './recordNodeStackVisitor'; +export * from './recordProvidedScopeVisitor'; export * from './removeDocsVisitor'; export * from './singleNodeVisitor'; export * from './staticVisitor'; diff --git a/packages/visitors-core/src/interceptFirstVisitVisitor.ts b/packages/visitors-core/src/interceptFirstVisitVisitor.ts index 79c3769d5..04fb6a540 100644 --- a/packages/visitors-core/src/interceptFirstVisitVisitor.ts +++ b/packages/visitors-core/src/interceptFirstVisitVisitor.ts @@ -5,15 +5,15 @@ import { Visitor } from './visitor'; export function interceptFirstVisitVisitor( visitor: Visitor, - interceptor: VisitorInterceptor, + interceptor: VisitorInterceptor, ): Visitor { let isFirstVisit = true; - return interceptVisitor(visitor, (node, next) => { + return interceptVisitor(visitor, (node, next, self) => { try { if (isFirstVisit) { isFirstVisit = false; - const result = interceptor(node, next); + const result = interceptor(node, next, self); isFirstVisit = true; return result; } diff --git a/packages/visitors-core/src/interceptVisitor.ts b/packages/visitors-core/src/interceptVisitor.ts index a0d44b684..d6c36745e 100644 --- a/packages/visitors-core/src/interceptVisitor.ts +++ b/packages/visitors-core/src/interceptVisitor.ts @@ -2,11 +2,15 @@ import { Node, NodeKind, REGISTERED_NODE_KINDS } from '@codama/nodes'; import { getVisitFunctionName, GetVisitorFunctionName, Visitor } from './visitor'; -export type VisitorInterceptor = (node: TNode, next: (node: TNode) => TReturn) => TReturn; +export type VisitorInterceptor = ( + node: TNode, + next: (node: TNode) => TReturn, + self: Visitor, +) => TReturn; export function interceptVisitor( visitor: Visitor, - interceptor: VisitorInterceptor, + interceptor: VisitorInterceptor, ): Visitor { const registeredVisitFunctions = REGISTERED_NODE_KINDS.map(getVisitFunctionName); @@ -22,7 +26,7 @@ export function interceptVisitor( castedKey, function interceptedVisitNode(this: Visitor, node: TNode) { const baseFunction = visitor[castedKey] as (node: TNode) => TReturn; - return interceptor(node, baseFunction.bind(this)); + return interceptor(node, baseFunction.bind(this), this); }, ], ]; diff --git a/packages/visitors-core/src/recordProvidedScopeVisitor.ts b/packages/visitors-core/src/recordProvidedScopeVisitor.ts new file mode 100644 index 000000000..cfde1f4fd --- /dev/null +++ b/packages/visitors-core/src/recordProvidedScopeVisitor.ts @@ -0,0 +1,28 @@ +import { isNode, type NodeKind } from '@codama/nodes'; + +import { interceptVisitor } from './interceptVisitor'; +import { ProvidedScope } from './ProvidedScope'; +import { Visitor } from './visitor'; + +/** + * Keep a {@link ProvidedScope} in sync with the traversal: every + * `instructionNode` with `provides` opens a frame for the duration of its + * visit, so injections within it (and its sub-instructions) resolve + * against the instructions enclosing them. + */ +export function recordProvidedScopeVisitor( + visitor: Visitor, + scope: ProvidedScope, +): Visitor { + return interceptVisitor(visitor, (node, next) => { + if (!isNode(node, 'instructionNode') || (node.provides ?? []).length === 0) { + return next(node); + } + scope.push(node.provides ?? []); + try { + return next(node); + } finally { + scope.pop(); + } + }); +} diff --git a/packages/visitors-core/test/NodeSelector.test.ts b/packages/visitors-core/test/NodeSelector.test.ts index 15c78c6c7..fa009f619 100644 --- a/packages/visitors-core/test/NodeSelector.test.ts +++ b/packages/visitors-core/test/NodeSelector.test.ts @@ -3,15 +3,13 @@ import { booleanTypeNode, definedTypeLinkNode, definedTypeNode, - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, enumTypeNode, + enumVariantTypeNode, errorNode, instructionAccountNode, - instructionArgumentNode, instructionNode, + integerTypeNode, Node, - numberTypeNode, optionTypeNode, programNode, publicKeyTypeNode, @@ -40,12 +38,12 @@ import { // rather than re-reading them back through the (optionally-typed) node graph. // splToken account fields. -const tokenAccountOwnerField = structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }); -const tokenAccountMintField = structFieldTypeNode({ name: 'mint', type: publicKeyTypeNode() }); -const tokenAccountAmountField = structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }); -const tokenDelegatedAmountOption = optionTypeNode(numberTypeNode('u64'), { prefix: numberTypeNode('u32') }); +const tokenAccountOwnerField = structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }); +const tokenAccountMintField = structFieldTypeNode({ identifier: 'mint', type: publicKeyTypeNode() }); +const tokenAccountAmountField = structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }); +const tokenDelegatedAmountOption = optionTypeNode(integerTypeNode('u64'), { prefix: integerTypeNode('u32') }); const tokenAccountDelegatedAmountField = structFieldTypeNode({ - name: 'delegatedAmount', + identifier: 'delegatedAmount', type: tokenDelegatedAmountOption, }); const tokenAccount = accountNode({ @@ -55,39 +53,42 @@ const tokenAccount = accountNode({ tokenAccountAmountField, tokenAccountDelegatedAmountField, ]), - name: 'token', + identifier: 'token', }); // splToken instruction. -const mintTokenAmountArgument = instructionArgumentNode({ name: 'amount', type: numberTypeNode('u64') }); +const mintTokenAmountField = structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }); const mintTokenInstruction = instructionNode({ accounts: [ - instructionAccountNode({ isSigner: false, isWritable: true, name: 'token' }), - instructionAccountNode({ isSigner: false, isWritable: true, name: 'mint' }), - instructionAccountNode({ isSigner: true, isWritable: false, name: 'mintAuthority' }), + instructionAccountNode({ identifier: 'token', isSigner: false, isWritable: true }), + instructionAccountNode({ identifier: 'mint', isSigner: false, isWritable: true }), + instructionAccountNode({ identifier: 'mintAuthority', isSigner: true, isWritable: false }), ], - arguments: [mintTokenAmountArgument], - name: 'mintToken', + data: structTypeNode([mintTokenAmountField]), + identifier: 'mintToken', }); const splTokenProgram = programNode({ accounts: [tokenAccount], errors: [ - errorNode({ code: 0, message: 'Invalid program ID', name: 'invalidProgramId' }), - errorNode({ code: 1, message: 'Invalid token owner', name: 'invalidTokenOwner' }), + errorNode({ code: 0, identifier: 'invalidProgramId', message: 'Invalid program ID' }), + errorNode({ code: 1, identifier: 'invalidTokenOwner', message: 'Invalid token owner' }), ], + identifier: 'splToken', instructions: [mintTokenInstruction], - name: 'splToken', publicKey: '1111', version: '1.0.0', }); // christmasProgram account fields. -const giftAccountOwnerField = structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }); -const giftAccountOpenedField = structFieldTypeNode({ name: 'opened', type: booleanTypeNode(numberTypeNode('u64')) }); -const giftAccountAmountField = structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }); +const giftAccountOwnerField = structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }); +const giftAccountOpenedField = structFieldTypeNode({ + identifier: 'opened', + type: booleanTypeNode({ size: integerTypeNode('u64') }), +}); +const giftAccountAmountField = structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }); const giftAccountWrappingPaperField = structFieldTypeNode({ - name: 'wrappingPaper', + identifier: 'wrappingPaper', type: definedTypeLinkNode('wrappingPaper'), }); const giftAccount = accountNode({ @@ -97,36 +98,38 @@ const giftAccount = accountNode({ giftAccountAmountField, giftAccountWrappingPaperField, ]), - name: 'gift', + identifier: 'gift', }); // christmasProgram wrappingPaper defined type. -const wrappingPaperGoldOwnerField = structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }); -const wrappingPaperBlueVariant = enumEmptyVariantTypeNode('blue'); -const wrappingPaperRedVariant = enumEmptyVariantTypeNode('red'); -const wrappingPaperEnumGold = enumStructVariantTypeNode('gold', structTypeNode([wrappingPaperGoldOwnerField])); +const wrappingPaperGoldOwnerField = structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }); +const wrappingPaperBlueVariant = enumVariantTypeNode('blue'); +const wrappingPaperRedVariant = enumVariantTypeNode('red'); +const wrappingPaperEnumGold = enumVariantTypeNode('gold', { + data: structTypeNode([wrappingPaperGoldOwnerField]), +}); const wrappingPaperEnum = enumTypeNode([wrappingPaperBlueVariant, wrappingPaperRedVariant, wrappingPaperEnumGold]); -const wrappingPaper = definedTypeNode({ name: 'wrappingPaper', type: wrappingPaperEnum }); +const wrappingPaper = definedTypeNode({ identifier: 'wrappingPaper', type: wrappingPaperEnum }); // christmasProgram instruction. -const openGiftGiftAccount = instructionAccountNode({ isSigner: false, isWritable: true, name: 'gift' }); -const openGiftOwnerAccount = instructionAccountNode({ isSigner: true, isWritable: false, name: 'owner' }); +const openGiftGiftAccount = instructionAccountNode({ identifier: 'gift', isSigner: false, isWritable: true }); +const openGiftOwnerAccount = instructionAccountNode({ identifier: 'owner', isSigner: true, isWritable: false }); const openGiftInstruction = instructionNode({ accounts: [openGiftGiftAccount, openGiftOwnerAccount], - name: 'openGift', + identifier: 'openGift', }); const christmasProgram = programNode({ accounts: [giftAccount], definedTypes: [wrappingPaper], - errors: [errorNode({ code: 0, message: 'Invalid program ID', name: 'invalidProgramId' })], + errors: [errorNode({ code: 0, identifier: 'invalidProgramId', message: 'Invalid program ID' })], + identifier: 'christmasProgram', instructions: [openGiftInstruction], - name: 'christmasProgram', publicKey: '2222', version: '1.0.0', }); -const tree = rootNode(splTokenProgram, [christmasProgram]); +const tree = rootNode(splTokenProgram, { additionalPrograms: [christmasProgram] }); const macro = (selector: NodeSelector, expectedSelected: Node[]) => { const title = @@ -165,29 +168,29 @@ const macro = (selector: NodeSelector, expectedSelected: Node[]) => { * [accountNode] token > [structTypeNode] * [structFieldTypeNode] owner > [publicKeyTypeNode] * [structFieldTypeNode] mint > [publicKeyTypeNode] - * [structFieldTypeNode] amount > [numberTypeNode] (u64) - * [structFieldTypeNode] delegatedAmount > [optionTypeNode] (prefix: [numberTypeNode] (u32)) > [numberTypeNode] (u64) + * [structFieldTypeNode] amount > [integerTypeNode] (u64) + * [structFieldTypeNode] delegatedAmount > [optionTypeNode] (prefix: [integerTypeNode] (u32)) > [integerTypeNode] (u64) * [instructionNode] mintToken * [instructionAccountNode] token * [instructionAccountNode] mint * [instructionAccountNode] mintAuthority - * [instructionArgumentNode] amount - * [numberTypeNode] (u64) + * [structTypeNode] + * [structFieldTypeNode] amount > [integerTypeNode] (u64) * [errorNode] invalidProgramId (0) * [errorNode] invalidTokenOwner (1) * [programNode] christmasProgram * [accountNode] gift > [structTypeNode] * [structFieldTypeNode] owner > [publicKeyTypeNode] - * [structFieldTypeNode] opened > [booleanTypeNode] > [numberTypeNode] (u64) - * [structFieldTypeNode] amount > [numberTypeNode] (u64) + * [structFieldTypeNode] opened > [booleanTypeNode] > [integerTypeNode] (u64) + * [structFieldTypeNode] amount > [integerTypeNode] (u64) * [structFieldTypeNode] wrappingPaper > [definedTypeLinkNode] wrappingPaper * [instructionNode] openGift * [instructionAccountNode] gift * [instructionAccountNode] owner * [definedTypeNode] wrappingPaper > [enumTypeNode] - * [enumEmptyVariantTypeNode] blue - * [enumEmptyVariantTypeNode] red - * [enumStructVariantTypeNode] gold > [structTypeNode] + * [enumVariantTypeNode] blue + * [enumVariantTypeNode] red + * [enumVariantTypeNode] gold > [structTypeNode] * [structFieldTypeNode] owner > [publicKeyTypeNode] * [errorNode] invalidProgramId (0) */ @@ -205,7 +208,7 @@ macro('[instructionNode].owner', [openGiftOwnerAccount]); macro('[accountNode].owner', [tokenAccountOwnerField, giftAccountOwnerField]); macro('[accountNode]token.owner', [tokenAccountOwnerField]); macro('christmasProgram.[accountNode].owner', [giftAccountOwnerField]); -macro('[programNode]christmasProgram.[definedTypeNode]wrappingPaper.[enumStructVariantTypeNode]gold.owner', [ +macro('[programNode]christmasProgram.[definedTypeNode]wrappingPaper.[enumVariantTypeNode]gold.owner', [ wrappingPaperGoldOwnerField, ]); macro('christmasProgram.wrappingPaper.gold.owner', [wrappingPaperGoldOwnerField]); @@ -218,14 +221,14 @@ macro('wrappingPaper.*', [ wrappingPaperBlueVariant, wrappingPaperRedVariant, wrappingPaperEnumGold, - wrappingPaperEnumGold.struct, + wrappingPaperEnumGold.data!, wrappingPaperGoldOwnerField, wrappingPaperGoldOwnerField.type, ]); macro('wrappingPaper.[structFieldTypeNode]', [wrappingPaperGoldOwnerField]); macro('wrappingPaper.blue', [wrappingPaperBlueVariant]); -macro('amount.*', [tokenAccountAmountField.type, mintTokenAmountArgument.type, giftAccountAmountField.type]); -macro('[instructionNode].amount.*', [mintTokenAmountArgument.type]); +macro('amount.*', [tokenAccountAmountField.type, mintTokenAmountField.type, giftAccountAmountField.type]); +macro('[instructionNode].amount.*', [mintTokenAmountField.type]); macro('[structFieldTypeNode].*', [ tokenAccountOwnerField.type, tokenAccountMintField.type, @@ -233,6 +236,7 @@ macro('[structFieldTypeNode].*', [ tokenAccountDelegatedAmountField.type, tokenDelegatedAmountOption.prefix, tokenDelegatedAmountOption.item, + mintTokenAmountField.type, giftAccountOwnerField.type, giftAccountOpenedField.type, giftAccountOpenedField.type.size, @@ -254,6 +258,6 @@ macro('[accountNode]gift.[publicKeyTypeNode|booleanTypeNode]', [ // Select using functions. macro( - path => isNodePath(path, 'numberTypeNode') && getLastNodeFromPath(path).format === 'u32', + path => isNodePath(path, 'integerTypeNode') && getLastNodeFromPath(path).format === 'u32', [tokenDelegatedAmountOption.prefix], ); diff --git a/packages/visitors-core/test/ProvidedScope.test.ts b/packages/visitors-core/test/ProvidedScope.test.ts new file mode 100644 index 000000000..79352942c --- /dev/null +++ b/packages/visitors-core/test/ProvidedScope.test.ts @@ -0,0 +1,185 @@ +import { + CODAMA_ERROR__UNEXPECTED_NODE_KIND, + CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, + CodamaError, +} from '@codama/errors'; +import { + accountValueNode, + identifierString, + injectedValueNode, + integerValueNode, + providedNode, + publicKeyTypeNode, + stringValueNode, + structFieldValueNode, + structValueNode, + VALUE_NODES, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { ProvidedScope } from '../src'; + +const kinds = ['accountValueNode' as const, 'integerValueNode' as const]; + +test('it resolves an injection to its provided node', () => { + const scope = new ProvidedScope([providedNode('decimals', integerValueNode('6'))]); + expect(scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds })).toEqual(integerValueNode('6')); +}); + +test('it lets inner frames shadow outer frames', () => { + const scope = new ProvidedScope( + [providedNode('authority', accountValueNode('outer'))], + [providedNode('authority', accountValueNode('inner'))], + ); + expect(scope.resolve(injectedValueNode({ key: 'authority' }), { kinds })).toEqual(accountValueNode('inner')); +}); + +test('it keeps outer frames visible', () => { + const scope = new ProvidedScope( + [providedNode('decimals', integerValueNode('6'))], + [providedNode('authority', accountValueNode('owner'))], + ); + expect(scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds })).toEqual(integerValueNode('6')); +}); + +test('it returns the fallback when no frame provides the key', () => { + const scope = new ProvidedScope(); + const node = injectedValueNode({ fallback: integerValueNode('9'), key: 'decimals' }); + expect(scope.resolve(node, { kinds })).toEqual(integerValueNode('9')); +}); + +test('it returns undefined when no frame provides the key and there is no fallback', () => { + const scope = new ProvidedScope(); + expect(scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds })).toBeUndefined(); +}); + +test('it resolves an injected fallback against the same frames', () => { + const scope = new ProvidedScope([providedNode('defaultDecimals', integerValueNode('9'))]); + const node = injectedValueNode({ fallback: injectedValueNode({ key: 'defaultDecimals' }), key: 'decimals' }); + expect(scope.resolve(node, { kinds })).toEqual(integerValueNode('9')); +}); + +test('it resolves a provided injection against the outer frames only', () => { + // Given an inner frame that re-provides a key by injecting that same key. + const scope = new ProvidedScope( + [providedNode('decimals', integerValueNode('6'))], + [providedNode('decimals', injectedValueNode({ key: 'decimals' }))], + ); + + // Then it resolves to the outer provider rather than looping. + expect(scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds })).toEqual(integerValueNode('6')); +}); + +test('it uses the fallback when a provided injection chain dead-ends', () => { + // Given a frame that provides a key by injecting a key nobody provides. + const scope = new ProvidedScope([providedNode('decimals', injectedValueNode({ key: 'missing' }))]); + + // Then the consumer's own fallback is used. + const node = injectedValueNode({ fallback: integerValueNode('9'), key: 'decimals' }); + expect(scope.resolve(node, { kinds })).toEqual(integerValueNode('9')); +}); + +test('it resolves injections nested within a node', () => { + // Given a struct value with an injected field. + const scope = new ProvidedScope([providedNode('age', integerValueNode('42'))]); + const node = structValueNode([ + structFieldValueNode('name', stringValueNode('Alice')), + structFieldValueNode('age', injectedValueNode({ key: 'age' })), + ]); + + // Then the nested injection is replaced by its provided node. + expect(scope.resolve(node, { kinds: VALUE_NODES })).toEqual( + structValueNode([ + structFieldValueNode('name', stringValueNode('Alice')), + structFieldValueNode('age', integerValueNode('42')), + ]), + ); +}); + +test('it treats a node with an unresolvable nested injection as unresolvable', () => { + // Given a struct value with an injected field that nothing provides. + const node = structValueNode([ + structFieldValueNode('name', stringValueNode('Alice')), + structFieldValueNode('age', injectedValueNode({ key: 'age' })), + ]); + + // Then the whole value is unresolvable rather than partially resolved. + expect(new ProvidedScope().resolve(node, { kinds: VALUE_NODES })).toBeUndefined(); +}); + +test('it returns a node without injections as-is', () => { + const node = structValueNode([structFieldValueNode('name', stringValueNode('Alice'))]); + expect(new ProvidedScope().resolve(node, { kinds: VALUE_NODES })).toBe(node); +}); + +test('it attributes a wrong-kind fallback of a provided injection to its provider', () => { + // Given a frame providing a key through an injection whose own fallback has the wrong kind. + const provider = providedNode('symbol', injectedValueNode({ fallback: integerValueNode('9'), key: 'missing' })); + const scope = new ProvidedScope([provider]); + + // Then the error names the provider. + expect(() => scope.resolve(injectedValueNode({ key: 'symbol' }), { kinds: ['stringValueNode'] })).toThrow( + new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { + expectedKinds: ['stringValueNode'], + key: identifierString('symbol'), + providedKind: 'integerValueNode', + provider, + }), + ); +}); + +test('it resolves injections within a provided node against the outer frames', () => { + // Given an inner frame providing a struct that injects a key both frames provide. + const scope = new ProvidedScope( + [providedNode('age', integerValueNode('1'))], + [ + providedNode('age', integerValueNode('2')), + providedNode('person', structValueNode([structFieldValueNode('age', injectedValueNode({ key: 'age' }))])), + ], + ); + + // Then the nested injection resolves outside the frame providing the struct. + expect(scope.resolve(injectedValueNode({ key: 'person' }), { kinds: VALUE_NODES })).toEqual( + structValueNode([structFieldValueNode('age', integerValueNode('1'))]), + ); +}); + +test('it throws when a fallback is not one of the expected kinds', () => { + const node = injectedValueNode({ fallback: integerValueNode('9'), key: 'symbol' }); + expect(() => new ProvidedScope().resolve(node, { kinds: ['stringValueNode'] })).toThrow( + new CodamaError(CODAMA_ERROR__UNEXPECTED_NODE_KIND, { + expectedKinds: ['stringValueNode'], + kind: 'integerValueNode', + node: integerValueNode('9'), + }), + ); +}); + +test('it throws when the provided node is not one of the expected kinds', () => { + const provider = providedNode('decimals', publicKeyTypeNode()); + const scope = new ProvidedScope([provider]); + expect(() => scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds })).toThrow( + new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { + expectedKinds: kinds, + key: identifierString('decimals'), + providedKind: 'publicKeyTypeNode', + provider, + }), + ); +}); + +test('it pushes and pops frames', () => { + const scope = new ProvidedScope(); + scope.push([providedNode('symbol', stringValueNode('SOL'))]); + expect(scope.get(identifierString('symbol'))).toEqual(providedNode('symbol', stringValueNode('SOL'))); + scope.pop(); + expect(scope.get(identifierString('symbol'))).toBeUndefined(); +}); + +test('it clones into an independent scope', () => { + const scope = new ProvidedScope([providedNode('symbol', stringValueNode('SOL'))]); + const clone = scope.clone(); + clone.pop(); + expect(scope.get(identifierString('symbol'))).toBeDefined(); + expect(clone.get(identifierString('symbol'))).toBeUndefined(); +}); diff --git a/packages/visitors-core/test/bottomUpTransformerVisitor.test.ts b/packages/visitors-core/test/bottomUpTransformerVisitor.test.ts index f79cc3956..b26fa06ff 100644 --- a/packages/visitors-core/test/bottomUpTransformerVisitor.test.ts +++ b/packages/visitors-core/test/bottomUpTransformerVisitor.test.ts @@ -1,7 +1,7 @@ import { definedTypeNode, isNode, - numberTypeNode, + integerTypeNode, programNode, publicKeyTypeNode, stringTypeNode, @@ -20,11 +20,11 @@ import { test('it can transform nodes into other nodes', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that transforms all number nodes into string nodes. const visitor = bottomUpTransformerVisitor([ - node => (isNode(node, 'numberTypeNode') ? stringTypeNode('utf8') : node), + node => (isNode(node, 'integerTypeNode') ? stringTypeNode('utf8') : node), ]); // When we visit the tree using that visitor. @@ -38,12 +38,12 @@ test('it can transform nodes into other nodes', () => { test('it can transform nodes using node selectors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that selects all number nodes and transforms them into string nodes. const visitor = bottomUpTransformerVisitor([ { - select: '[numberTypeNode]', + select: '[integerTypeNode]', transform: () => stringTypeNode('utf8'), }, ]); @@ -59,12 +59,12 @@ test('it can transform nodes using node selectors', () => { test('it can create partial transformer visitors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that wraps every node into another tuple node // but that does not transform public key nodes. const visitor = bottomUpTransformerVisitor([node => (isNode(node, TYPE_NODES) ? tupleTypeNode([node]) : node)], { - keys: ['tupleTypeNode', 'numberTypeNode'], + keys: ['tupleTypeNode', 'integerTypeNode'], }); // When we visit the tree using that visitor. @@ -74,8 +74,8 @@ test('it can create partial transformer visitors', () => { expect(result).toEqual( tupleTypeNode([ tupleTypeNode([ - tupleTypeNode([numberTypeNode('u32')]), - tupleTypeNode([tupleTypeNode([tupleTypeNode([numberTypeNode('u32')]), publicKeyTypeNode()])]), + tupleTypeNode([integerTypeNode('u32')]), + tupleTypeNode([tupleTypeNode([tupleTypeNode([integerTypeNode('u32')]), publicKeyTypeNode()])]), ]), ]), ); @@ -87,10 +87,10 @@ test('it can create partial transformer visitors', () => { test('it can be used to delete nodes', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that deletes all number nodes. - const visitor = bottomUpTransformerVisitor([{ select: '[numberTypeNode]', transform: () => null }]); + const visitor = bottomUpTransformerVisitor([{ select: '[integerTypeNode]', transform: () => null }]); // When we visit the tree using that visitor. const result = visit(node, visitor); @@ -101,14 +101,14 @@ test('it can be used to delete nodes', () => { test('it can transform nodes using multiple node selectors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that uses two node selectors such that // - the first one selects all number nodes, and // - the second one selects all nodes with more than one ancestor. const visitor = bottomUpTransformerVisitor([ { - select: ['[numberTypeNode]', path => path.length > 2], + select: ['[integerTypeNode]', path => path.length > 2], transform: () => stringTypeNode('utf8'), }, ]); @@ -118,24 +118,24 @@ test('it can transform nodes using multiple node selectors', () => { // Then we expect both node selectors to have been applied. expect(result).toEqual( - tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([stringTypeNode('utf8'), publicKeyTypeNode()])]), + tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([stringTypeNode('utf8'), publicKeyTypeNode()])]), ); }); test('it can start from an existing stack', () => { // Given the following tuple node inside a program node. - const tuple = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); - const myTuple = definedTypeNode({ name: 'myTuple', type: tuple }); + const tuple = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); + const myTuple = definedTypeNode({ identifier: 'myTuple', type: tuple }); const program = programNode({ definedTypes: [myTuple], - name: 'myProgram', + identifier: 'myProgram', publicKey: '1111', }); // And a transformer that removes all number nodes // from programs whose public key is '1111'. const transformer: BottomUpNodeTransformerWithSelector = { - select: ['[numberTypeNode]', path => findProgramNodeFromPath(path)?.publicKey === '1111'], + select: ['[integerTypeNode]', path => findProgramNodeFromPath(path)?.publicKey === '1111'], transform: () => null, }; diff --git a/packages/visitors-core/test/deleteNodesVisitor.test.ts b/packages/visitors-core/test/deleteNodesVisitor.test.ts index 5eff4c445..5bd080d38 100644 --- a/packages/visitors-core/test/deleteNodesVisitor.test.ts +++ b/packages/visitors-core/test/deleteNodesVisitor.test.ts @@ -1,14 +1,14 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { deleteNodesVisitor, visit } from '../src'; test('it can delete nodes using selectors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a visitor that deletes all number nodes. - const visitor = deleteNodesVisitor(['[numberTypeNode]']); + const visitor = deleteNodesVisitor(['[integerTypeNode]']); // When we visit the tree using that visitor. const result = visit(node, visitor); @@ -19,12 +19,12 @@ test('it can delete nodes using selectors', () => { test('it can create partial visitors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a visitor that deletes all number nodes and public key nodes // but does not support public key nodes. - const visitor = deleteNodesVisitor(['[numberTypeNode]', '[publicKeyTypeNode]'], { - keys: ['tupleTypeNode', 'numberTypeNode'], + const visitor = deleteNodesVisitor(['[integerTypeNode]', '[publicKeyTypeNode]'], { + keys: ['tupleTypeNode', 'integerTypeNode'], }); // When we visit the tree using that visitor. diff --git a/packages/visitors-core/test/extendVisitor.test.ts b/packages/visitors-core/test/extendVisitor.test.ts index aabb605b0..8504b2880 100644 --- a/packages/visitors-core/test/extendVisitor.test.ts +++ b/packages/visitors-core/test/extendVisitor.test.ts @@ -1,12 +1,12 @@ import { CODAMA_ERROR__VISITORS__CANNOT_EXTEND_MISSING_VISIT_FUNCTION, CodamaError } from '@codama/errors'; -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { extendVisitor, mergeVisitor, visit, voidVisitor } from '../src'; test('it returns a new visitor that extends a subset of visits with a next function', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u64'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u64'), publicKeyTypeNode()])]); // And an extended sum visitor that adds an extra 10 to tuple and public key nodes. const baseVisitor = mergeVisitor( @@ -30,7 +30,7 @@ test('it returns a new visitor that extends a subset of visits with a next funct test('it can visit itself using the exposed self argument', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u64'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u64'), publicKeyTypeNode()])]); // And an extended sum visitor that only visit the first item of tuple nodes. const baseVisitor = mergeVisitor( @@ -56,12 +56,12 @@ test('it cannot extends nodes that are not supported by the base visitor', () => // Then we expect an error when we try to extend other nodes for that visitor. expect(() => extendVisitor(baseVisitor, { - // @ts-expect-error NumberTypeNode is not part of the base visitor. - visitNumberType: () => undefined, + // @ts-expect-error IntegerTypeNode is not part of the base visitor. + visitIntegerType: () => undefined, }), ).toThrow( new CodamaError(CODAMA_ERROR__VISITORS__CANNOT_EXTEND_MISSING_VISIT_FUNCTION, { - visitFunction: 'visitNumberType', + visitFunction: 'visitIntegerType', }), ); }); diff --git a/packages/visitors-core/test/getByteSizeVisitor.test.ts b/packages/visitors-core/test/getByteSizeVisitor.test.ts index 992857390..4ca591ebc 100644 --- a/packages/visitors-core/test/getByteSizeVisitor.test.ts +++ b/packages/visitors-core/test/getByteSizeVisitor.test.ts @@ -1,44 +1,42 @@ import { accountNode, - amountTypeNode, + addTypeNodeTransforms, arrayTypeNode, booleanTypeNode, bytesTypeNode, bytesValueNode, constantValueNode, constantValueNodeFromString, - dateTimeTypeNode, definedTypeLinkNode, definedTypeNode, - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - enumTupleVariantTypeNode, + durationTypeNode, enumTypeNode, + enumVariantTypeNode, fixedCountNode, - fixedSizeTypeNode, + fixedPointTypeNode, + fixedSizeTransformNode, + floatTypeNode, GetNodeFromKind, - hiddenPrefixTypeNode, - hiddenSuffixTypeNode, - instructionArgumentNode, + hiddenPrefixTransformNode, + hiddenSuffixTransformNode, + integerTypeNode, + integerValueNode, + IntegerFormat, instructionNode, mapTypeNode, - NumberFormat, - numberTypeNode, - numberValueNode, optionTypeNode, - postOffsetTypeNode, + postOffsetTransformNode, + preOffsetTransformNode, prefixedCountNode, - preOffsetTypeNode, programLinkNode, programNode, publicKeyTypeNode, remainderCountNode, remainderOptionTypeNode, rootNode, - sentinelTypeNode, + sentinelTransformNode, setTypeNode, - sizePrefixTypeNode, - solAmountTypeNode, + sizePrefixTransformNode, stringTypeNode, stringValueNode, structFieldTypeNode, @@ -84,38 +82,31 @@ describe('accountNode', () => { expectSize( accountNode({ data: structTypeNode([ - structFieldTypeNode({ name: 'mint', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ identifier: 'mint', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }), ]), - name: 'token', + identifier: 'token', }), 32 + 32 + 8, ); }); }); -describe('amountTypeNode', () => { - test('it delegates to the underlying number type', () => { - expectSize(amountTypeNode(numberTypeNode('u64'), 2, 'GBP'), 8); - expectSize(amountTypeNode(numberTypeNode('shortU16'), 2, 'GBP'), null); - }); -}); - describe('arrayTypeNode', () => { test('it returns a size if the count is fixed and the inner type is sized', () => { - expectSize(arrayTypeNode(numberTypeNode('u16'), fixedCountNode(3)), 2 * 3); + expectSize(arrayTypeNode(integerTypeNode('u16'), fixedCountNode(3)), 2 * 3); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { expectSize(arrayTypeNode(stringTypeNode('utf8'), fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - expectSize(arrayTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u8'))), null); - expectSize(arrayTypeNode(numberTypeNode('u16'), remainderCountNode()), null); + expectSize(arrayTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u8'))), null); + expectSize(arrayTypeNode(integerTypeNode('u16'), remainderCountNode()), null); }); test('it returns null if the inner type is unsized', () => { expectSize(arrayTypeNode(stringTypeNode('utf8'), fixedCountNode(3)), null); - expectSize(arrayTypeNode(stringTypeNode('utf8'), prefixedCountNode(numberTypeNode('u8'))), null); + expectSize(arrayTypeNode(stringTypeNode('utf8'), prefixedCountNode(integerTypeNode('u8'))), null); expectSize(arrayTypeNode(stringTypeNode('utf8'), remainderCountNode()), null); }); test('it returns 0 if the inner type size is 0 and the count is fixed', () => { @@ -125,8 +116,8 @@ describe('arrayTypeNode', () => { expectSize(arrayTypeNode(tupleTypeNode([]), remainderCountNode()), 0); }); test('it returns the prefix size if the inner type size is 0 and the count is prefixed', () => { - expectSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('u32'))), 4); - expectSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('shortU16'))), null); + expectSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('u32'))), 4); + expectSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('shortU16'))), null); }); }); @@ -135,8 +126,8 @@ describe('booleanTypeNode', () => { expectSize(booleanTypeNode(), 1); }); test('it delegates to the custom boolean size otherwise', () => { - expectSize(booleanTypeNode(numberTypeNode('u64')), 8); - expectSize(booleanTypeNode(numberTypeNode('shortU16')), null); + expectSize(booleanTypeNode({ size: integerTypeNode('u64') }), 8); + expectSize(booleanTypeNode({ size: integerTypeNode('shortU16') }), null); }); }); @@ -148,8 +139,14 @@ describe('bytesTypeNode', () => { describe('constantValueNode', () => { test('it returns the type size if fixed', () => { - expectSize(constantValueNode(numberTypeNode('u32'), numberValueNode(42)), 4); - expectSize(constantValueNode(fixedSizeTypeNode(stringTypeNode('utf8'), 42), stringValueNode('Hello')), 42); + expectSize(constantValueNode(integerTypeNode('u32'), integerValueNode('42')), 4); + expectSize( + constantValueNode( + addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(42)]), + stringValueNode('Hello'), + ), + 42, + ); }); test('it returns the size of byte value nodes when used with a base16 encoding', () => { expectSize(constantValueNode(bytesTypeNode(), bytesValueNode('base16', '11223344')), 4); @@ -159,25 +156,32 @@ describe('constantValueNode', () => { }); }); -describe('dateTimeTypeNode', () => { +describe('durationTypeNode', () => { + test('it delegates to the underlying number type', () => { + expectSize(durationTypeNode(integerTypeNode('u64')), 8); + expectSize(durationTypeNode(integerTypeNode('shortU16')), null); + }); +}); + +describe('fixedPointTypeNode', () => { test('it delegates to the underlying number type', () => { - expectSize(dateTimeTypeNode(numberTypeNode('u64')), 8); - expectSize(dateTimeTypeNode(numberTypeNode('shortU16')), null); + expectSize(fixedPointTypeNode(integerTypeNode('u64'), 9), 8); + expectSize(fixedPointTypeNode(integerTypeNode('shortU16'), 9), null); }); }); describe('definedTypeNode', () => { test('it returns the size of the inner type', () => { - expectSize(definedTypeNode({ name: 'fixed', type: numberTypeNode('u32') }), 4); - expectSize(definedTypeNode({ name: 'variable', type: stringTypeNode('utf8') }), null); + expectSize(definedTypeNode({ identifier: 'fixed', type: integerTypeNode('u32') }), 4); + expectSize(definedTypeNode({ identifier: 'variable', type: stringTypeNode('utf8') }), null); }); }); describe('definedTypeLinkNode', () => { test('it returns the size of the type being linked', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: numberTypeNode('u64') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: integerTypeNode('u64') })], + identifier: 'myProgram', publicKey: '1111', }); @@ -185,21 +189,21 @@ describe('definedTypeLinkNode', () => { }); test('it returns null if the linked type is variable', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: stringTypeNode('utf8') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: stringTypeNode('utf8') })], + identifier: 'myProgram', publicKey: '1111', }); expectSizeWithContext([context, definedTypeLinkNode('myType')], null); }); test('it returns null if the linked type cannot be found', () => { - const context = programNode({ name: 'myProgram', publicKey: '1111' }); + const context = programNode({ identifier: 'myProgram', publicKey: '1111' }); expectSizeWithContext([context, definedTypeLinkNode('myMissingType')], null); }); test('it returns null if the linked type is circular', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: definedTypeLinkNode('myType') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: definedTypeLinkNode('myType') })], + identifier: 'myProgram', publicKey: '1111', }); @@ -207,23 +211,23 @@ describe('definedTypeLinkNode', () => { }); test('it follows linked nodes using the correct paths when jumping between programs', () => { const typeA = definedTypeNode({ - name: 'typeA', - type: definedTypeLinkNode('typeB1', programLinkNode('programB')), + identifier: 'typeA', + type: definedTypeLinkNode('typeB1', { program: programLinkNode('programB') }), }); const programA = programNode({ definedTypes: [typeA], - name: 'programA', + identifier: 'programA', publicKey: '1111', }); const programB = programNode({ definedTypes: [ - definedTypeNode({ name: 'typeB1', type: definedTypeLinkNode('typeB2') }), - definedTypeNode({ name: 'typeB2', type: numberTypeNode('u64') }), + definedTypeNode({ identifier: 'typeB1', type: definedTypeLinkNode('typeB2') }), + definedTypeNode({ identifier: 'typeB2', type: integerTypeNode('u64') }), ], - name: 'programB', + identifier: 'programB', publicKey: '2222', }); - const context = rootNode(programA, [programB]); + const context = rootNode(programA, { additionalPrograms: [programB] }); expectSizeWithContext([context, programA, typeA], 8); }); @@ -231,17 +235,13 @@ describe('definedTypeLinkNode', () => { describe('enumTypeNode', () => { test('it returns 1 by default for scalar enums', () => { - expectSize( - enumTypeNode([enumEmptyVariantTypeNode('A'), enumEmptyVariantTypeNode('B'), enumEmptyVariantTypeNode('C')]), - 1, - ); + expectSize(enumTypeNode([enumVariantTypeNode('A'), enumVariantTypeNode('B'), enumVariantTypeNode('C')]), 1); }); test('it returns the custom size for scalar enums', () => { expectSize( - enumTypeNode( - [enumEmptyVariantTypeNode('A'), enumEmptyVariantTypeNode('B'), enumEmptyVariantTypeNode('C')], - { size: numberTypeNode('u64') }, - ), + enumTypeNode([enumVariantTypeNode('A'), enumVariantTypeNode('B'), enumVariantTypeNode('C')], { + size: integerTypeNode('u64'), + }), 8, ); }); @@ -250,18 +250,17 @@ describe('enumTypeNode', () => { enumTypeNode( [ // 4 bytes - enumTupleVariantTypeNode('A', tupleTypeNode([numberTypeNode('u32')])), + enumVariantTypeNode('A', { data: tupleTypeNode([integerTypeNode('u32')]) }), // 4 bytes - enumStructVariantTypeNode( - 'B', - structTypeNode([ - structFieldTypeNode({ name: 'x', type: numberTypeNode('u16') }), - structFieldTypeNode({ name: 'y', type: numberTypeNode('u16') }), + enumVariantTypeNode('B', { + data: structTypeNode([ + structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u16') }), + structFieldTypeNode({ identifier: 'y', type: integerTypeNode('u16') }), ]), - ), + }), ], // 8 bytes prefix - { size: numberTypeNode('u64') }, + { size: integerTypeNode('u64') }, ), 8 + 4, ); @@ -269,103 +268,103 @@ describe('enumTypeNode', () => { test('it returns null if variants have different sizes', () => { expectSize( enumTypeNode([ - enumTupleVariantTypeNode('A', tupleTypeNode([numberTypeNode('u16')])), // 2 bytes - enumTupleVariantTypeNode('B', tupleTypeNode([numberTypeNode('u32')])), // 4 bytes + enumVariantTypeNode('A', { data: tupleTypeNode([integerTypeNode('u16')]) }), // 2 bytes + enumVariantTypeNode('B', { data: tupleTypeNode([integerTypeNode('u32')]) }), // 4 bytes ]), null, ); }); test('it returns null if at least one variant is unsized', () => { - expectSize(enumTypeNode([enumTupleVariantTypeNode('A', tupleTypeNode([stringTypeNode('utf8')]))]), null); + expectSize(enumTypeNode([enumVariantTypeNode('A', { data: tupleTypeNode([stringTypeNode('utf8')]) })]), null); }); }); -describe('fixedSizeTypeNode', () => { - test('it returns the fixed size assigned on the node', () => { - expectSize(fixedSizeTypeNode(numberTypeNode('u8'), 32), 32); - expectSize(fixedSizeTypeNode(stringTypeNode('utf8'), 32), 32); +describe('fixedSizeTransformNode', () => { + test('it returns the fixed size assigned by the transform', () => { + expectSize(addTypeNodeTransforms(integerTypeNode('u8'), [fixedSizeTransformNode(32)]), 32); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)]), 32); }); }); -describe('hiddenPrefixTypeNode', () => { +describe('hiddenPrefixTransformNode', () => { test('it returns the sum of all prefixes and the inner item if all of them are fixed', () => { const prefix1 = constantValueNodeFromString('base16', '2222'); const prefix2 = constantValueNodeFromString('base16', '333333'); - expectSize(hiddenPrefixTypeNode(numberTypeNode('u32'), [prefix1, prefix2]), 2 + 3 + 4); + expectSize( + addTypeNodeTransforms(integerTypeNode('u32'), [hiddenPrefixTransformNode([prefix1, prefix2])]), + 2 + 3 + 4, + ); }); test('it returns null if the inner item is variable', () => { const prefix = constantValueNodeFromString('base16', 'ffff'); - expectSize(hiddenPrefixTypeNode(stringTypeNode('utf8'), [prefix]), null); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [hiddenPrefixTransformNode([prefix])]), null); }); }); -describe('hiddenSuffixTypeNode', () => { +describe('hiddenSuffixTransformNode', () => { test('it returns the sum of all suffixes and the inner item if all of them are fixed', () => { const suffix1 = constantValueNodeFromString('base16', '2222'); const suffix2 = constantValueNodeFromString('base16', '333333'); - expectSize(hiddenSuffixTypeNode(numberTypeNode('u32'), [suffix1, suffix2]), 4 + 2 + 3); + expectSize( + addTypeNodeTransforms(integerTypeNode('u32'), [hiddenSuffixTransformNode([suffix1, suffix2])]), + 4 + 2 + 3, + ); }); test('it returns null if the inner item is variable', () => { const suffix = constantValueNodeFromString('base16', 'ffff'); - expectSize(hiddenSuffixTypeNode(stringTypeNode('utf8'), [suffix]), null); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [hiddenSuffixTransformNode([suffix])]), null); }); }); describe('instructionNode', () => { - test('it returns the total size of all arguments in the instruction', () => { + test('it returns the total size of all data fields in the instruction', () => { expectSize( instructionNode({ - arguments: [ - instructionArgumentNode({ name: 'lamports', type: numberTypeNode('u64') }), - instructionArgumentNode({ name: 'space', type: numberTypeNode('u32') }), - ], - name: 'createAccount', + data: structTypeNode([ + structFieldTypeNode({ identifier: 'lamports', type: integerTypeNode('u64') }), + structFieldTypeNode({ identifier: 'space', type: integerTypeNode('u32') }), + ]), + identifier: 'createAccount', }), 8 + 4, ); }); - test('it returns null if any argument is unsized', () => { + test('it returns null if any data field is unsized', () => { expectSize( instructionNode({ - arguments: [ - instructionArgumentNode({ name: 'lamports', type: numberTypeNode('u64') }), - instructionArgumentNode({ name: 'name', type: stringTypeNode('utf8') }), - ], - name: 'createAccount', + data: structTypeNode([ + structFieldTypeNode({ identifier: 'lamports', type: integerTypeNode('u64') }), + structFieldTypeNode({ identifier: 'name', type: stringTypeNode('utf8') }), + ]), + identifier: 'createAccount', }), null, ); }); }); -describe('instructionArgumentNode', () => { - test('it returns the size of the argument type', () => { - expectSize(instructionArgumentNode({ name: 'lamports', type: numberTypeNode('u64') }), 8); - }); -}); - describe('mapTypeNode', () => { test('it returns a size if the count is fixed and the inner type is sized', () => { - const key = numberTypeNode('u8'); // Fixed - const value = numberTypeNode('u16'); // Fixed + const key = integerTypeNode('u8'); // Fixed + const value = integerTypeNode('u16'); // Fixed expectSize(mapTypeNode(key, value, fixedCountNode(3)), (1 + 2) * 3); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { const key = stringTypeNode('utf8'); // Variable - const value = numberTypeNode('u16'); // Fixed + const value = integerTypeNode('u16'); // Fixed expectSize(mapTypeNode(key, value, fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - const key = numberTypeNode('u8'); // Fixed - const value = numberTypeNode('u16'); // Fixed - expectSize(mapTypeNode(key, value, prefixedCountNode(numberTypeNode('u8'))), null); + const key = integerTypeNode('u8'); // Fixed + const value = integerTypeNode('u16'); // Fixed + expectSize(mapTypeNode(key, value, prefixedCountNode(integerTypeNode('u8'))), null); expectSize(mapTypeNode(key, value, remainderCountNode()), null); }); test('it returns null if the inner type is unsized', () => { - const key = numberTypeNode('u8'); + const key = integerTypeNode('u8'); const value = stringTypeNode('utf8'); expectSize(mapTypeNode(key, value, fixedCountNode(3)), null); - expectSize(mapTypeNode(key, value, prefixedCountNode(numberTypeNode('u8'))), null); + expectSize(mapTypeNode(key, value, prefixedCountNode(integerTypeNode('u8'))), null); expectSize(mapTypeNode(key, value, remainderCountNode()), null); }); test('it returns 0 if the inner type size is 0 and the count is fixed', () => { @@ -378,12 +377,12 @@ describe('mapTypeNode', () => { }); test('it returns the prefix size if the inner type size is 0 and the count is prefixed', () => { const zeroSizeType = tupleTypeNode([]); - expectSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(numberTypeNode('u32'))), 4); - expectSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(numberTypeNode('shortU16'))), null); + expectSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(integerTypeNode('u32'))), 4); + expectSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(integerTypeNode('shortU16'))), null); }); }); -describe('numberTypeNode', () => { +describe('integerTypeNode', () => { test.each([ ['u8', 1], ['i8', 1], @@ -395,72 +394,121 @@ describe('numberTypeNode', () => { ['i64', 8], ['u128', 16], ['i128', 16], - ['f32', 4], - ['f64', 8], - ])('it returns the size of %s numbers', (format, expectedSize) => { - expectSize(numberTypeNode(format as NumberFormat), expectedSize); + ] as const)('it returns the size of %s numbers', (format, expectedSize) => { + expectSize(integerTypeNode(format as IntegerFormat), expectedSize); }); test('it returns null if the format is shortU16', () => { - expectSize(numberTypeNode('shortU16'), null); + expectSize(integerTypeNode('shortU16'), null); + }); +}); + +describe('floatTypeNode', () => { + test('it returns the size of f32 numbers', () => { + expectSize(floatTypeNode('f32'), 4); + }); + test('it returns the size of f64 numbers', () => { + expectSize(floatTypeNode('f64'), 8); }); }); describe('optionTypeNode', () => { test('it returns the sum of the prefix and the inner item sizes if both of them are fixed', () => { - expectSize(optionTypeNode(numberTypeNode('u32'), { fixed: true }), 5); - expectSize(optionTypeNode(numberTypeNode('u32'), { fixed: true, prefix: numberTypeNode('u16') }), 6); + expectSize(optionTypeNode(integerTypeNode('u32'), { fixed: true }), 5); + expectSize(optionTypeNode(integerTypeNode('u32'), { fixed: true, prefix: integerTypeNode('u16') }), 6); }); test('it returns null if the inner item is not fixed', () => { expectSize(optionTypeNode(stringTypeNode('utf8'), { fixed: true }), null); }); test('it returns null if the prefixed is not fixed', () => { - expectSize(optionTypeNode(numberTypeNode('u32'), { fixed: true, prefix: numberTypeNode('shortU16') }), null); + expectSize(optionTypeNode(integerTypeNode('u32'), { fixed: true, prefix: integerTypeNode('shortU16') }), null); }); test('it returns null if the option is not fixed', () => { - expectSize(optionTypeNode(numberTypeNode('u32')), null); - expectSize(optionTypeNode(numberTypeNode('u32'), { prefix: numberTypeNode('u16') }), null); + expectSize(optionTypeNode(integerTypeNode('u32')), null); + expectSize(optionTypeNode(integerTypeNode('u32'), { prefix: integerTypeNode('u16') }), null); }); }); -describe('postOffsetTypeNode', () => { +describe('postOffsetTransformNode', () => { test('it increases the size by the offset when using a padded offset', () => { - expectSize(postOffsetTypeNode(numberTypeNode('u16'), 10, 'padded'), 12); + expectSize( + addTypeNodeTransforms(integerTypeNode('u16'), [postOffsetTransformNode(10, { strategy: 'padded' })]), + 12, + ); }); test('it returns null if the inner item is not fixed', () => { - expectSize(postOffsetTypeNode(stringTypeNode('utf8'), 4, 'padded'), null); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(4, { strategy: 'padded' })]), + null, + ); }); test('it returns the size of the inner item for other offset strategies', () => { // Fixed. - expectSize(postOffsetTypeNode(numberTypeNode('u8'), 42), 1); - expectSize(postOffsetTypeNode(numberTypeNode('u8'), 42, 'absolute'), 1); - expectSize(postOffsetTypeNode(numberTypeNode('u8'), 42, 'preOffset'), 1); - expectSize(postOffsetTypeNode(numberTypeNode('u8'), 42, 'relative'), 1); + expectSize(addTypeNodeTransforms(integerTypeNode('u8'), [postOffsetTransformNode(42)]), 1); + expectSize( + addTypeNodeTransforms(integerTypeNode('u8'), [postOffsetTransformNode(42, { strategy: 'absolute' })]), + 1, + ); + expectSize( + addTypeNodeTransforms(integerTypeNode('u8'), [postOffsetTransformNode(42, { strategy: 'preOffset' })]), + 1, + ); + expectSize( + addTypeNodeTransforms(integerTypeNode('u8'), [postOffsetTransformNode(42, { strategy: 'relative' })]), + 1, + ); // Variable. - expectSize(postOffsetTypeNode(stringTypeNode('utf8'), 42), null); - expectSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), null); - expectSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'preOffset'), null); - expectSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'relative'), null); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42)]), null); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'absolute' })]), + null, + ); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'preOffset' })]), + null, + ); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'relative' })]), + null, + ); }); }); -describe('preOffsetTypeNode', () => { +describe('preOffsetTransformNode', () => { test('it increases the size by the offset when using a padded offset', () => { - expectSize(preOffsetTypeNode(numberTypeNode('u16'), 10, 'padded'), 12); + expectSize( + addTypeNodeTransforms(integerTypeNode('u16'), [preOffsetTransformNode(10, { strategy: 'padded' })]), + 12, + ); }); test('it returns null if the inner item is not fixed', () => { - expectSize(preOffsetTypeNode(stringTypeNode('utf8'), 4, 'padded'), null); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(4, { strategy: 'padded' })]), + null, + ); }); test('it returns the size of the inner item for other offset strategies', () => { // Fixed. - expectSize(preOffsetTypeNode(numberTypeNode('u8'), 42), 1); - expectSize(preOffsetTypeNode(numberTypeNode('u8'), 42, 'absolute'), 1); - expectSize(preOffsetTypeNode(numberTypeNode('u8'), 42, 'relative'), 1); + expectSize(addTypeNodeTransforms(integerTypeNode('u8'), [preOffsetTransformNode(42)]), 1); + expectSize( + addTypeNodeTransforms(integerTypeNode('u8'), [preOffsetTransformNode(42, { strategy: 'absolute' })]), + 1, + ); + expectSize( + addTypeNodeTransforms(integerTypeNode('u8'), [preOffsetTransformNode(42, { strategy: 'relative' })]), + 1, + ); // Variable. - expectSize(preOffsetTypeNode(stringTypeNode('utf8'), 42), null); - expectSize(preOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), null); - expectSize(preOffsetTypeNode(stringTypeNode('utf8'), 42, 'relative'), null); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42)]), null); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42, { strategy: 'absolute' })]), + null, + ); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42, { strategy: 'relative' })]), + null, + ); }); }); @@ -475,36 +523,36 @@ describe('remainderOptionTypeNode', () => { expectSize(remainderOptionTypeNode(tupleTypeNode([])), 0); }); test('it returns null in all other cases', () => { - expectSize(remainderOptionTypeNode(numberTypeNode('u16')), null); + expectSize(remainderOptionTypeNode(integerTypeNode('u16')), null); expectSize(remainderOptionTypeNode(stringTypeNode('utf8')), null); }); }); -describe('sentinelTypeNode', () => { +describe('sentinelTransformNode', () => { test('it returns the inner type and the sentinel size if both of them are fixed', () => { const sentinel = constantValueNodeFromString('base16', 'ffff'); - expectSize(sentinelTypeNode(numberTypeNode('u32'), sentinel), 6); + expectSize(addTypeNodeTransforms(integerTypeNode('u32'), [sentinelTransformNode(sentinel)]), 6); }); test('it returns null if the inner type is variable', () => { const sentinel = constantValueNodeFromString('base16', 'ffff'); - expectSize(sentinelTypeNode(stringTypeNode('utf8'), sentinel), null); + expectSize(addTypeNodeTransforms(stringTypeNode('utf8'), [sentinelTransformNode(sentinel)]), null); }); }); describe('setTypeNode', () => { test('it returns a size if the count is fixed and the inner type is sized', () => { - expectSize(setTypeNode(numberTypeNode('u16'), fixedCountNode(3)), 2 * 3); + expectSize(setTypeNode(integerTypeNode('u16'), fixedCountNode(3)), 2 * 3); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { expectSize(setTypeNode(stringTypeNode('utf8'), fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - expectSize(setTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u8'))), null); - expectSize(setTypeNode(numberTypeNode('u16'), remainderCountNode()), null); + expectSize(setTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u8'))), null); + expectSize(setTypeNode(integerTypeNode('u16'), remainderCountNode()), null); }); test('it returns null if the inner type is unsized', () => { expectSize(setTypeNode(stringTypeNode('utf8'), fixedCountNode(3)), null); - expectSize(setTypeNode(stringTypeNode('utf8'), prefixedCountNode(numberTypeNode('u8'))), null); + expectSize(setTypeNode(stringTypeNode('utf8'), prefixedCountNode(integerTypeNode('u8'))), null); expectSize(setTypeNode(stringTypeNode('utf8'), remainderCountNode()), null); }); test('it returns 0 if the inner type size is 0 and the count is fixed', () => { @@ -514,27 +562,26 @@ describe('setTypeNode', () => { expectSize(setTypeNode(tupleTypeNode([]), remainderCountNode()), 0); }); test('it returns the prefix size if the inner type size is 0 and the count is prefixed', () => { - expectSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('u32'))), 4); - expectSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('shortU16'))), null); + expectSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('u32'))), 4); + expectSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('shortU16'))), null); }); }); -describe('sizePrefixTypeNode', () => { +describe('sizePrefixTransformNode', () => { test('it returns the size of the size prefix if the inner type size is 0', () => { - expectSize(sizePrefixTypeNode(tupleTypeNode([]), numberTypeNode('u32')), 4); + expectSize(addTypeNodeTransforms(tupleTypeNode([]), [sizePrefixTransformNode(integerTypeNode('u32'))]), 4); }); test('it returns the sum of the prefix and the inner type if both are fixed', () => { - expectSize(sizePrefixTypeNode(publicKeyTypeNode(), numberTypeNode('u32')), 4 + 32); + expectSize( + addTypeNodeTransforms(publicKeyTypeNode(), [sizePrefixTransformNode(integerTypeNode('u32'))]), + 4 + 32, + ); }); test('it returns null if the inner type is variable', () => { - expectSize(sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')), null); - }); -}); - -describe('solAmountTypeNode', () => { - test('it delegates to the underlying number type', () => { - expectSize(solAmountTypeNode(numberTypeNode('u64')), 8); - expectSize(solAmountTypeNode(numberTypeNode('shortU16')), null); + expectSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [sizePrefixTransformNode(integerTypeNode('u32'))]), + null, + ); }); }); @@ -549,8 +596,8 @@ describe('stringTypeNode', () => { describe('structFieldTypeNode', () => { test('it returns the size of the inner type', () => { - expectSize(structFieldTypeNode({ name: 'fixed', type: numberTypeNode('u32') }), 4); - expectSize(structFieldTypeNode({ name: 'variable', type: stringTypeNode('utf8') }), null); + expectSize(structFieldTypeNode({ identifier: 'fixed', type: integerTypeNode('u32') }), 4); + expectSize(structFieldTypeNode({ identifier: 'variable', type: stringTypeNode('utf8') }), null); }); }); @@ -558,8 +605,11 @@ describe('structTypeNode', () => { test('it returns the sum of fields if all fields are fixed size', () => { expectSize( structTypeNode([ - structFieldTypeNode({ name: 'age', type: numberTypeNode('u32') }), - structFieldTypeNode({ name: 'firstname', type: fixedSizeTypeNode(stringTypeNode('utf8'), 42) }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u32') }), + structFieldTypeNode({ + identifier: 'firstname', + type: addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(42)]), + }), ]), 4 + 42, ); @@ -567,8 +617,8 @@ describe('structTypeNode', () => { test('it returns null if any field is variable', () => { expectSize( structTypeNode([ - structFieldTypeNode({ name: 'age', type: numberTypeNode('u32') }), - structFieldTypeNode({ name: 'firstname', type: stringTypeNode('utf8') }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u32') }), + structFieldTypeNode({ identifier: 'firstname', type: stringTypeNode('utf8') }), ]), null, ); @@ -577,10 +627,10 @@ describe('structTypeNode', () => { describe('tupleTypeNode', () => { test('it returns the sum of all sizes if all elements are fixed', () => { - expectSize(tupleTypeNode([numberTypeNode('u16'), numberTypeNode('u32')]), 2 + 4); + expectSize(tupleTypeNode([integerTypeNode('u16'), integerTypeNode('u32')]), 2 + 4); }); test('it returns null if any item is variable', () => { - expectSize(tupleTypeNode([numberTypeNode('u16'), stringTypeNode('utf8')]), null); + expectSize(tupleTypeNode([integerTypeNode('u16'), stringTypeNode('utf8')]), null); }); }); @@ -593,10 +643,10 @@ describe('zeroableOptionTypeNode', () => { }); test('it returns the inner item size if it matches the zero value when provided', () => { const zeroValue = constantValueNodeFromString('base16', 'ffffffff'); - expectSize(zeroableOptionTypeNode(numberTypeNode('u32'), zeroValue), 4); + expectSize(zeroableOptionTypeNode(integerTypeNode('u32'), { zeroValue }), 4); }); test('it returns null if the provided zero value does not match the inner item size', () => { const zeroValue = constantValueNodeFromString('base16', 'ffffffff'); - expectSize(zeroableOptionTypeNode(numberTypeNode('u64'), zeroValue), null); + expectSize(zeroableOptionTypeNode(integerTypeNode('u64'), { zeroValue }), null); }); }); diff --git a/packages/visitors-core/test/getDebugStringVisitor.test.ts b/packages/visitors-core/test/getDebugStringVisitor.test.ts index b522e29b1..ec2a15c7f 100644 --- a/packages/visitors-core/test/getDebugStringVisitor.test.ts +++ b/packages/visitors-core/test/getDebugStringVisitor.test.ts @@ -1,103 +1,100 @@ import { - enumEmptyVariantTypeNode, + addTypeNodeTransforms, enumTypeNode, - numberTypeNode, + enumVariantTypeNode, + instructionStatusNode, + integerTypeNode, optionTypeNode, publicKeyTypeNode, - sizePrefixTypeNode, + sizePrefixTransformNode, stringTypeNode, structFieldTypeNode, structTypeNode, + textNode, tupleTypeNode, } from '@codama/nodes'; import { expect, test } from 'vitest'; import { getDebugStringVisitor, visit } from '../src'; -test('it returns a string representing the main information of a node for debugging purposes', () => { - // Given the following tree. - const node = tupleTypeNode([ - numberTypeNode('u32'), +const getTree = () => + tupleTypeNode([ + integerTypeNode('u32'), structTypeNode([ structFieldTypeNode({ - name: 'firstname', - type: sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u64')), + identifier: 'firstname', + type: addTypeNodeTransforms(stringTypeNode('utf8'), [sizePrefixTransformNode(integerTypeNode('u64'))]), }), - structFieldTypeNode({ name: 'age', type: numberTypeNode('u32') }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u32') }), structFieldTypeNode({ - name: 'wallet', + identifier: 'wallet', type: optionTypeNode(publicKeyTypeNode(), { - prefix: numberTypeNode('u16'), + prefix: integerTypeNode('u16'), }), }), structFieldTypeNode({ - name: 'industry', + identifier: 'industry', type: enumTypeNode([ - enumEmptyVariantTypeNode('programming'), - enumEmptyVariantTypeNode('crypto'), - enumEmptyVariantTypeNode('music'), + enumVariantTypeNode('programming'), + enumVariantTypeNode('crypto'), + enumVariantTypeNode('music'), ]), }), ]), ]); - // When we get its unique hash string. +test('it returns a string representing the main information of a node for debugging purposes', () => { + // Given the following tree. + const node = getTree(); + + // When we get its debug string. const result = visit(node, getDebugStringVisitor()); // Then we expect the following string. expect(result).toEqual( - 'tupleTypeNode(numberTypeNode[u32], structTypeNode(structFieldTypeNode[firstname](sizePrefixTypeNode(numberTypeNode[u64], stringTypeNode[utf8])), structFieldTypeNode[age](numberTypeNode[u32]), structFieldTypeNode[wallet](optionTypeNode(numberTypeNode[u16], publicKeyTypeNode)), structFieldTypeNode[industry](enumTypeNode(numberTypeNode[u8], enumEmptyVariantTypeNode[programming], enumEmptyVariantTypeNode[crypto], enumEmptyVariantTypeNode[music]))))', + 'tupleTypeNode(integerTypeNode[u32], structTypeNode(structFieldTypeNode[firstname](stringTypeNode[utf8](sizePrefixTransformNode(integerTypeNode[u64]))), structFieldTypeNode[age](integerTypeNode[u32]), structFieldTypeNode[wallet](optionTypeNode(integerTypeNode[u16], publicKeyTypeNode)), structFieldTypeNode[industry](enumTypeNode(integerTypeNode[u8], enumVariantTypeNode[programming], enumVariantTypeNode[crypto], enumVariantTypeNode[music]))))', ); }); test('it can create indented strings', () => { // Given the following tree. - const node = tupleTypeNode([ - numberTypeNode('u32'), - structTypeNode([ - structFieldTypeNode({ - name: 'firstname', - type: sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u64')), - }), - structFieldTypeNode({ name: 'age', type: numberTypeNode('u32') }), - structFieldTypeNode({ - name: 'wallet', - type: optionTypeNode(publicKeyTypeNode(), { - prefix: numberTypeNode('u16'), - }), - }), - structFieldTypeNode({ - name: 'industry', - type: enumTypeNode([ - enumEmptyVariantTypeNode('programming'), - enumEmptyVariantTypeNode('crypto'), - enumEmptyVariantTypeNode('music'), - ]), - }), - ]), - ]); + const node = getTree(); - // When we get its unique hash string. + // When we get its indented debug string. const result = visit(node, getDebugStringVisitor({ indent: true })); // Then we expect the following string. expect(result).toEqual(`tupleTypeNode -| numberTypeNode [u32] +| integerTypeNode [u32] | structTypeNode | | structFieldTypeNode [firstname] -| | | sizePrefixTypeNode -| | | | numberTypeNode [u64] -| | | | stringTypeNode [utf8] +| | | stringTypeNode [utf8] +| | | | sizePrefixTransformNode +| | | | | integerTypeNode [u64] | | structFieldTypeNode [age] -| | | numberTypeNode [u32] +| | | integerTypeNode [u32] | | structFieldTypeNode [wallet] | | | optionTypeNode -| | | | numberTypeNode [u16] +| | | | integerTypeNode [u16] | | | | publicKeyTypeNode | | structFieldTypeNode [industry] | | | enumTypeNode -| | | | numberTypeNode [u8] -| | | | enumEmptyVariantTypeNode [programming] -| | | | enumEmptyVariantTypeNode [crypto] -| | | | enumEmptyVariantTypeNode [music]`); +| | | | integerTypeNode [u8] +| | | | enumVariantTypeNode [programming] +| | | | enumVariantTypeNode [crypto] +| | | | enumVariantTypeNode [music]`); +}); + +test('it reads the content of a textNode-valued attribute', () => { + // Given an instruction status whose message is a rich textNode. + const node = instructionStatusNode('deprecated', { + message: textNode({ content: 'Use newInstruction instead' }), + }); + + // When we get its indented debug string. + const result = visit(node, getDebugStringVisitor({ indent: true })); + + // Then the message content is surfaced (and the textNode child is visited). + expect(result).toEqual(`instructionStatusNode [deprecated.Use newInstruction instead] +| textNode [Use newInstruction instead]`); }); diff --git a/packages/visitors-core/test/getMaxByteSizeVisitor.test.ts b/packages/visitors-core/test/getMaxByteSizeVisitor.test.ts index 9ec96bc2e..5d59c5caa 100644 --- a/packages/visitors-core/test/getMaxByteSizeVisitor.test.ts +++ b/packages/visitors-core/test/getMaxByteSizeVisitor.test.ts @@ -1,43 +1,42 @@ import { accountNode, - amountTypeNode, + addTypeNodeTransforms, arrayTypeNode, booleanTypeNode, bytesTypeNode, bytesValueNode, constantValueNode, constantValueNodeFromString, - dateTimeTypeNode, definedTypeLinkNode, definedTypeNode, - enumEmptyVariantTypeNode, - enumTupleVariantTypeNode, + durationTypeNode, enumTypeNode, + enumVariantTypeNode, fixedCountNode, - fixedSizeTypeNode, + fixedPointTypeNode, + fixedSizeTransformNode, + floatTypeNode, GetNodeFromKind, - hiddenPrefixTypeNode, - hiddenSuffixTypeNode, - instructionArgumentNode, + hiddenPrefixTransformNode, + hiddenSuffixTransformNode, + integerTypeNode, + integerValueNode, + IntegerFormat, instructionNode, mapTypeNode, - NumberFormat, - numberTypeNode, - numberValueNode, optionTypeNode, - postOffsetTypeNode, + postOffsetTransformNode, + preOffsetTransformNode, prefixedCountNode, - preOffsetTypeNode, programLinkNode, programNode, publicKeyTypeNode, remainderCountNode, remainderOptionTypeNode, rootNode, - sentinelTypeNode, + sentinelTransformNode, setTypeNode, - sizePrefixTypeNode, - solAmountTypeNode, + sizePrefixTransformNode, someValueNode, stringTypeNode, stringValueNode, @@ -84,37 +83,30 @@ describe('accountNode', () => { expectMaxSize( accountNode({ data: structTypeNode([ - structFieldTypeNode({ name: 'authority', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'maxSupply', type: optionTypeNode(numberTypeNode('u64')) }), + structFieldTypeNode({ identifier: 'authority', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'maxSupply', type: optionTypeNode(integerTypeNode('u64')) }), ]), - name: 'mint', + identifier: 'mint', }), 32 + 9, ); }); }); -describe('amountTypeNode', () => { - test('it delegates to the underlying number type', () => { - expectMaxSize(amountTypeNode(numberTypeNode('u64'), 2, 'GBP'), 8); - expectMaxSize(amountTypeNode(numberTypeNode('shortU16'), 2, 'GBP'), 3); - }); -}); - describe('arrayTypeNode', () => { test('it multiplies the max size of the inner item with the fixed count', () => { - expectMaxSize(arrayTypeNode(optionTypeNode(numberTypeNode('u32')), fixedCountNode(3)), 5 * 3); + expectMaxSize(arrayTypeNode(optionTypeNode(integerTypeNode('u32')), fixedCountNode(3)), 5 * 3); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { expectMaxSize(arrayTypeNode(stringTypeNode('utf8'), fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - expectMaxSize(arrayTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u8'))), null); - expectMaxSize(arrayTypeNode(numberTypeNode('u16'), remainderCountNode()), null); + expectMaxSize(arrayTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u8'))), null); + expectMaxSize(arrayTypeNode(integerTypeNode('u16'), remainderCountNode()), null); }); test('it returns null if the inner type has no max size', () => { expectMaxSize(arrayTypeNode(stringTypeNode('utf8'), fixedCountNode(3)), null); - expectMaxSize(arrayTypeNode(stringTypeNode('utf8'), prefixedCountNode(numberTypeNode('u8'))), null); + expectMaxSize(arrayTypeNode(stringTypeNode('utf8'), prefixedCountNode(integerTypeNode('u8'))), null); expectMaxSize(arrayTypeNode(stringTypeNode('utf8'), remainderCountNode()), null); }); test('it returns 0 if the inner type max size is 0 and the count is fixed', () => { @@ -124,8 +116,8 @@ describe('arrayTypeNode', () => { expectMaxSize(arrayTypeNode(tupleTypeNode([]), remainderCountNode()), 0); }); test('it returns the prefix max size if the inner type size is 0 and the count is prefixed', () => { - expectMaxSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('u32'))), 4); - expectMaxSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('shortU16'))), 3); + expectMaxSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('u32'))), 4); + expectMaxSize(arrayTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('shortU16'))), 3); }); }); @@ -134,8 +126,8 @@ describe('booleanTypeNode', () => { expectMaxSize(booleanTypeNode(), 1); }); test('it delegates to the custom boolean size otherwise', () => { - expectMaxSize(booleanTypeNode(numberTypeNode('u64')), 8); - expectMaxSize(booleanTypeNode(numberTypeNode('shortU16')), 3); + expectMaxSize(booleanTypeNode({ size: integerTypeNode('u64') }), 8); + expectMaxSize(booleanTypeNode({ size: integerTypeNode('shortU16') }), 3); }); }); @@ -147,8 +139,17 @@ describe('bytesTypeNode', () => { describe('constantValueNode', () => { test('it returns the type size if it has a max size', () => { - expectMaxSize(constantValueNode(optionTypeNode(numberTypeNode('u32')), someValueNode(numberValueNode(42))), 5); - expectMaxSize(constantValueNode(fixedSizeTypeNode(stringTypeNode('utf8'), 42), stringValueNode('Hello')), 42); + expectMaxSize( + constantValueNode(optionTypeNode(integerTypeNode('u32')), someValueNode(integerValueNode('42'))), + 5, + ); + expectMaxSize( + constantValueNode( + addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(42)]), + stringValueNode('Hello'), + ), + 42, + ); }); test('it returns the size of byte value nodes when used with a base16 encoding', () => { expectMaxSize(constantValueNode(bytesTypeNode(), bytesValueNode('base16', '11223344')), 4); @@ -158,25 +159,32 @@ describe('constantValueNode', () => { }); }); -describe('dateTimeTypeNode', () => { +describe('durationTypeNode', () => { + test('it delegates to the underlying number type', () => { + expectMaxSize(durationTypeNode(integerTypeNode('u64')), 8); + expectMaxSize(durationTypeNode(integerTypeNode('shortU16')), 3); + }); +}); + +describe('fixedPointTypeNode', () => { test('it delegates to the underlying number type', () => { - expectMaxSize(dateTimeTypeNode(numberTypeNode('u64')), 8); - expectMaxSize(dateTimeTypeNode(numberTypeNode('shortU16')), 3); + expectMaxSize(fixedPointTypeNode(integerTypeNode('u64'), 9), 8); + expectMaxSize(fixedPointTypeNode(integerTypeNode('shortU16'), 9), 3); }); }); describe('definedTypeNode', () => { test('it returns the size of the inner type', () => { - expectMaxSize(definedTypeNode({ name: 'withMaxSize', type: numberTypeNode('shortU16') }), 3); - expectMaxSize(definedTypeNode({ name: 'withoutMaxSize', type: stringTypeNode('utf8') }), null); + expectMaxSize(definedTypeNode({ identifier: 'withMaxSize', type: integerTypeNode('shortU16') }), 3); + expectMaxSize(definedTypeNode({ identifier: 'withoutMaxSize', type: stringTypeNode('utf8') }), null); }); }); describe('definedTypeLinkNode', () => { test('it returns the max size of the type being linked', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: numberTypeNode('shortU16') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: integerTypeNode('shortU16') })], + identifier: 'myProgram', publicKey: '1111', }); @@ -184,21 +192,21 @@ describe('definedTypeLinkNode', () => { }); test('it returns null if the linked type has no max size', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: stringTypeNode('utf8') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: stringTypeNode('utf8') })], + identifier: 'myProgram', publicKey: '1111', }); expectMaxSizeWithContext([context, definedTypeLinkNode('myType')], null); }); test('it returns null if the linked type cannot be found', () => { - const context = programNode({ name: 'myProgram', publicKey: '1111' }); + const context = programNode({ identifier: 'myProgram', publicKey: '1111' }); expectMaxSizeWithContext([context, definedTypeLinkNode('myMissingType')], null); }); test('it returns null if the linked type is circular', () => { const context = programNode({ - definedTypes: [definedTypeNode({ name: 'myType', type: definedTypeLinkNode('myType') })], - name: 'myProgram', + definedTypes: [definedTypeNode({ identifier: 'myType', type: definedTypeLinkNode('myType') })], + identifier: 'myProgram', publicKey: '1111', }); @@ -206,23 +214,23 @@ describe('definedTypeLinkNode', () => { }); test('it follows linked nodes using the correct paths when jumping between programs', () => { const typeA = definedTypeNode({ - name: 'typeA', - type: definedTypeLinkNode('typeB1', programLinkNode('programB')), + identifier: 'typeA', + type: definedTypeLinkNode('typeB1', { program: programLinkNode('programB') }), }); const programA = programNode({ definedTypes: [typeA], - name: 'programA', + identifier: 'programA', publicKey: '1111', }); const programB = programNode({ definedTypes: [ - definedTypeNode({ name: 'typeB1', type: definedTypeLinkNode('typeB2') }), - definedTypeNode({ name: 'typeB2', type: numberTypeNode('shortU16') }), + definedTypeNode({ identifier: 'typeB1', type: definedTypeLinkNode('typeB2') }), + definedTypeNode({ identifier: 'typeB2', type: integerTypeNode('shortU16') }), ], - name: 'programB', + identifier: 'programB', publicKey: '2222', }); - const context = rootNode(programA, [programB]); + const context = rootNode(programA, { additionalPrograms: [programB] }); expectMaxSizeWithContext([context, programA, typeA], 3); }); @@ -230,17 +238,13 @@ describe('definedTypeLinkNode', () => { describe('enumTypeNode', () => { test('it returns 1 by default for scalar enums', () => { - expectMaxSize( - enumTypeNode([enumEmptyVariantTypeNode('A'), enumEmptyVariantTypeNode('B'), enumEmptyVariantTypeNode('C')]), - 1, - ); + expectMaxSize(enumTypeNode([enumVariantTypeNode('A'), enumVariantTypeNode('B'), enumVariantTypeNode('C')]), 1); }); test('it returns the custom size for scalar enums', () => { expectMaxSize( - enumTypeNode( - [enumEmptyVariantTypeNode('A'), enumEmptyVariantTypeNode('B'), enumEmptyVariantTypeNode('C')], - { size: numberTypeNode('shortU16') }, - ), + enumTypeNode([enumVariantTypeNode('A'), enumVariantTypeNode('B'), enumVariantTypeNode('C')], { + size: integerTypeNode('shortU16'), + }), 3, ); }); @@ -248,105 +252,108 @@ describe('enumTypeNode', () => { expectMaxSize( enumTypeNode( [ - enumTupleVariantTypeNode('A', tupleTypeNode([numberTypeNode('u16')])), // 2 bytes - enumTupleVariantTypeNode('B', tupleTypeNode([numberTypeNode('u32')])), // 4 bytes + enumVariantTypeNode('A', { data: tupleTypeNode([integerTypeNode('u16')]) }), // 2 bytes + enumVariantTypeNode('B', { data: tupleTypeNode([integerTypeNode('u32')]) }), // 4 bytes ], - { size: numberTypeNode('u64') }, + { size: integerTypeNode('u64') }, ), 8 + 4, ); }); test('it returns null if at least one variant has no max size', () => { - expectMaxSize(enumTypeNode([enumTupleVariantTypeNode('A', tupleTypeNode([stringTypeNode('utf8')]))]), null); + expectMaxSize( + enumTypeNode([enumVariantTypeNode('A', { data: tupleTypeNode([stringTypeNode('utf8')]) })]), + null, + ); }); }); -describe('fixedSizeTypeNode', () => { - test('it returns the fixed size assigned on the node', () => { - expectMaxSize(fixedSizeTypeNode(numberTypeNode('u8'), 32), 32); - expectMaxSize(fixedSizeTypeNode(stringTypeNode('utf8'), 32), 32); +describe('fixedSizeTransformNode', () => { + test('it returns the fixed size assigned by the transform', () => { + expectMaxSize(addTypeNodeTransforms(integerTypeNode('u8'), [fixedSizeTransformNode(32)]), 32); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)]), 32); }); }); -describe('hiddenPrefixTypeNode', () => { +describe('hiddenPrefixTransformNode', () => { test('it returns the sum of all prefixes and the inner item if all of them have a max size', () => { const prefix1 = constantValueNodeFromString('base16', '2222'); const prefix2 = constantValueNodeFromString('base16', '333333'); - expectMaxSize(hiddenPrefixTypeNode(numberTypeNode('shortU16'), [prefix1, prefix2]), 2 + 3 + 3); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [hiddenPrefixTransformNode([prefix1, prefix2])]), + 2 + 3 + 3, + ); }); test('it returns null if the inner item has no max size', () => { const prefix = constantValueNodeFromString('base16', 'ffff'); - expectMaxSize(hiddenPrefixTypeNode(stringTypeNode('utf8'), [prefix]), null); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [hiddenPrefixTransformNode([prefix])]), null); }); }); -describe('hiddenSuffixTypeNode', () => { +describe('hiddenSuffixTransformNode', () => { test('it returns the sum of all suffixes and the inner item if all of them have a max size', () => { const suffix1 = constantValueNodeFromString('base16', '2222'); const suffix2 = constantValueNodeFromString('base16', '333333'); - expectMaxSize(hiddenSuffixTypeNode(numberTypeNode('shortU16'), [suffix1, suffix2]), 3 + 2 + 3); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [hiddenSuffixTransformNode([suffix1, suffix2])]), + 3 + 2 + 3, + ); }); test('it returns null if the inner item has no max size', () => { const suffix = constantValueNodeFromString('base16', 'ffff'); - expectMaxSize(hiddenSuffixTypeNode(stringTypeNode('utf8'), [suffix]), null); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [hiddenSuffixTransformNode([suffix])]), null); }); }); describe('instructionNode', () => { - test('it returns the total max size of all arguments in the instruction', () => { + test('it returns the total max size of all data fields in the instruction', () => { expectMaxSize( instructionNode({ - arguments: [ - instructionArgumentNode({ name: 'lamports', type: optionTypeNode(numberTypeNode('u64')) }), - instructionArgumentNode({ name: 'space', type: numberTypeNode('shortU16') }), - ], - name: 'createAccount', + data: structTypeNode([ + structFieldTypeNode({ identifier: 'lamports', type: optionTypeNode(integerTypeNode('u64')) }), + structFieldTypeNode({ identifier: 'space', type: integerTypeNode('shortU16') }), + ]), + identifier: 'createAccount', }), 9 + 3, ); }); - test('it returns null if any argument has no max size', () => { + test('it returns null if any data field has no max size', () => { expectMaxSize( instructionNode({ - arguments: [ - instructionArgumentNode({ name: 'lamports', type: numberTypeNode('u64') }), - instructionArgumentNode({ name: 'name', type: stringTypeNode('utf8') }), - ], - name: 'createAccount', + data: structTypeNode([ + structFieldTypeNode({ identifier: 'lamports', type: integerTypeNode('u64') }), + structFieldTypeNode({ identifier: 'name', type: stringTypeNode('utf8') }), + ]), + identifier: 'createAccount', }), null, ); }); }); -describe('instructionArgumentNode', () => { - test('it returns the max size of the argument type', () => { - expectMaxSize(instructionArgumentNode({ name: 'lamports', type: numberTypeNode('shortU16') }), 3); - }); -}); - describe('mapTypeNode', () => { test('it multiplies the max size of the inner item with the fixed count', () => { - const key = numberTypeNode('u8'); - const value = optionTypeNode(numberTypeNode('u16')); + const key = integerTypeNode('u8'); + const value = optionTypeNode(integerTypeNode('u16')); expectMaxSize(mapTypeNode(key, value, fixedCountNode(4)), (1 + 3) * 4); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { const key = stringTypeNode('utf8'); - const value = numberTypeNode('u16'); + const value = integerTypeNode('u16'); expectMaxSize(mapTypeNode(key, value, fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - const key = numberTypeNode('u8'); - const value = numberTypeNode('u16'); - expectMaxSize(mapTypeNode(key, value, prefixedCountNode(numberTypeNode('u8'))), null); + const key = integerTypeNode('u8'); + const value = integerTypeNode('u16'); + expectMaxSize(mapTypeNode(key, value, prefixedCountNode(integerTypeNode('u8'))), null); expectMaxSize(mapTypeNode(key, value, remainderCountNode()), null); }); test('it returns null if the inner type has no max size', () => { - const key = numberTypeNode('u8'); + const key = integerTypeNode('u8'); const value = stringTypeNode('utf8'); expectMaxSize(mapTypeNode(key, value, fixedCountNode(3)), null); - expectMaxSize(mapTypeNode(key, value, prefixedCountNode(numberTypeNode('u8'))), null); + expectMaxSize(mapTypeNode(key, value, prefixedCountNode(integerTypeNode('u8'))), null); expectMaxSize(mapTypeNode(key, value, remainderCountNode()), null); }); test('it returns 0 if the inner type max size is 0 and the count is fixed', () => { @@ -359,12 +366,12 @@ describe('mapTypeNode', () => { }); test('it returns the prefix max size if the inner type size is 0 and the count is prefixed', () => { const zeroSizeType = tupleTypeNode([]); - expectMaxSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(numberTypeNode('u32'))), 4); - expectMaxSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(numberTypeNode('shortU16'))), 3); + expectMaxSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(integerTypeNode('u32'))), 4); + expectMaxSize(mapTypeNode(zeroSizeType, zeroSizeType, prefixedCountNode(integerTypeNode('shortU16'))), 3); }); }); -describe('numberTypeNode', () => { +describe('integerTypeNode', () => { test.each([ ['u8', 1], ['i8', 1], @@ -376,64 +383,115 @@ describe('numberTypeNode', () => { ['i64', 8], ['u128', 16], ['i128', 16], - ['f32', 4], - ['f64', 8], ['shortU16', 3], - ])('it returns the size of %s numbers', (format, expectedSize) => { - expectMaxSize(numberTypeNode(format as NumberFormat), expectedSize); + ] as const)('it returns the size of %s numbers', (format, expectedSize) => { + expectMaxSize(integerTypeNode(format as IntegerFormat), expectedSize); + }); +}); + +describe('floatTypeNode', () => { + test('it returns the size of f32 numbers', () => { + expectMaxSize(floatTypeNode('f32'), 4); + }); + test('it returns the size of f64 numbers', () => { + expectMaxSize(floatTypeNode('f64'), 8); }); }); describe('optionTypeNode', () => { test('it returns the max size of the inner item plus 1 by default', () => { - expectMaxSize(optionTypeNode(numberTypeNode('u32')), 1 + 4); - expectMaxSize(optionTypeNode(numberTypeNode('u32'), { fixed: true }), 1 + 4); + expectMaxSize(optionTypeNode(integerTypeNode('u32')), 1 + 4); + expectMaxSize(optionTypeNode(integerTypeNode('u32'), { fixed: true }), 1 + 4); }); test('it returns the sum of the prefix and inner item max sizes', () => { - expectMaxSize(optionTypeNode(numberTypeNode('u32'), { prefix: numberTypeNode('u16') }), 2 + 4); - expectMaxSize(optionTypeNode(numberTypeNode('u32'), { fixed: true, prefix: numberTypeNode('u16') }), 2 + 4); + expectMaxSize(optionTypeNode(integerTypeNode('u32'), { prefix: integerTypeNode('u16') }), 2 + 4); + expectMaxSize(optionTypeNode(integerTypeNode('u32'), { fixed: true, prefix: integerTypeNode('u16') }), 2 + 4); }); }); -describe('postOffsetTypeNode', () => { +describe('postOffsetTransformNode', () => { test('it increases the max size by the offset when using a padded offset', () => { - expectMaxSize(postOffsetTypeNode(numberTypeNode('shortU16'), 10, 'padded'), 13); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [postOffsetTransformNode(10, { strategy: 'padded' })]), + 13, + ); }); - test('it returns null if the inner item is has no max size', () => { - expectMaxSize(postOffsetTypeNode(stringTypeNode('utf8'), 4, 'padded'), null); + test('it returns null if the inner item has no max size', () => { + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(4, { strategy: 'padded' })]), + null, + ); }); test('it returns the max size of the inner item for other offset strategies', () => { // Fixed. - expectMaxSize(postOffsetTypeNode(numberTypeNode('shortU16'), 42), 3); - expectMaxSize(postOffsetTypeNode(numberTypeNode('shortU16'), 42, 'absolute'), 3); - expectMaxSize(postOffsetTypeNode(numberTypeNode('shortU16'), 42, 'preOffset'), 3); - expectMaxSize(postOffsetTypeNode(numberTypeNode('shortU16'), 42, 'relative'), 3); + expectMaxSize(addTypeNodeTransforms(integerTypeNode('shortU16'), [postOffsetTransformNode(42)]), 3); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [postOffsetTransformNode(42, { strategy: 'absolute' })]), + 3, + ); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [ + postOffsetTransformNode(42, { strategy: 'preOffset' }), + ]), + 3, + ); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [postOffsetTransformNode(42, { strategy: 'relative' })]), + 3, + ); // Variable. - expectMaxSize(postOffsetTypeNode(stringTypeNode('utf8'), 42), null); - expectMaxSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), null); - expectMaxSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'preOffset'), null); - expectMaxSize(postOffsetTypeNode(stringTypeNode('utf8'), 42, 'relative'), null); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42)]), null); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'absolute' })]), + null, + ); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'preOffset' })]), + null, + ); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [postOffsetTransformNode(42, { strategy: 'relative' })]), + null, + ); }); }); -describe('preOffsetTypeNode', () => { +describe('preOffsetTransformNode', () => { test('it increases the max size by the offset when using a padded offset', () => { - expectMaxSize(preOffsetTypeNode(numberTypeNode('shortU16'), 10, 'padded'), 13); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [preOffsetTransformNode(10, { strategy: 'padded' })]), + 13, + ); }); test('it returns null if the inner item has no max size', () => { - expectMaxSize(preOffsetTypeNode(stringTypeNode('utf8'), 4, 'padded'), null); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(4, { strategy: 'padded' })]), + null, + ); }); test('it returns the max size of the inner item for other offset strategies', () => { // Fixed. - expectMaxSize(preOffsetTypeNode(numberTypeNode('shortU16'), 42), 3); - expectMaxSize(preOffsetTypeNode(numberTypeNode('shortU16'), 42, 'absolute'), 3); - expectMaxSize(preOffsetTypeNode(numberTypeNode('shortU16'), 42, 'relative'), 3); + expectMaxSize(addTypeNodeTransforms(integerTypeNode('shortU16'), [preOffsetTransformNode(42)]), 3); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [preOffsetTransformNode(42, { strategy: 'absolute' })]), + 3, + ); + expectMaxSize( + addTypeNodeTransforms(integerTypeNode('shortU16'), [preOffsetTransformNode(42, { strategy: 'relative' })]), + 3, + ); // Variable. - expectMaxSize(preOffsetTypeNode(stringTypeNode('utf8'), 42), null); - expectMaxSize(preOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), null); - expectMaxSize(preOffsetTypeNode(stringTypeNode('utf8'), 42, 'relative'), null); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42)]), null); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42, { strategy: 'absolute' })]), + null, + ); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [preOffsetTransformNode(42, { strategy: 'relative' })]), + null, + ); }); }); @@ -448,36 +506,36 @@ describe('remainderOptionTypeNode', () => { expectMaxSize(remainderOptionTypeNode(tupleTypeNode([])), 0); }); test('it returns null in all other cases', () => { - expectMaxSize(remainderOptionTypeNode(numberTypeNode('u16')), null); + expectMaxSize(remainderOptionTypeNode(integerTypeNode('u16')), null); expectMaxSize(remainderOptionTypeNode(stringTypeNode('utf8')), null); }); }); -describe('sentinelTypeNode', () => { +describe('sentinelTransformNode', () => { test('it returns the sum of the inner type and the sentinel max sizes if both of them exist', () => { const sentinel = constantValueNodeFromString('base16', 'ffff'); - expectMaxSize(sentinelTypeNode(numberTypeNode('shortU16'), sentinel), 2 + 3); + expectMaxSize(addTypeNodeTransforms(integerTypeNode('shortU16'), [sentinelTransformNode(sentinel)]), 2 + 3); }); test('it returns null if the inner type has no max size', () => { const sentinel = constantValueNodeFromString('base16', 'ffff'); - expectMaxSize(sentinelTypeNode(stringTypeNode('utf8'), sentinel), null); + expectMaxSize(addTypeNodeTransforms(stringTypeNode('utf8'), [sentinelTransformNode(sentinel)]), null); }); }); describe('setTypeNode', () => { test('it multiplies the max size of the inner item with the fixed count', () => { - expectMaxSize(setTypeNode(optionTypeNode(numberTypeNode('u32')), fixedCountNode(3)), 5 * 3); + expectMaxSize(setTypeNode(optionTypeNode(integerTypeNode('u32')), fixedCountNode(3)), 5 * 3); }); test('it returns 0 if the count is 0 and the inner type is unsized', () => { expectMaxSize(setTypeNode(stringTypeNode('utf8'), fixedCountNode(0)), 0); }); test('it returns null if the count is not fixed', () => { - expectMaxSize(setTypeNode(numberTypeNode('u16'), prefixedCountNode(numberTypeNode('u8'))), null); - expectMaxSize(setTypeNode(numberTypeNode('u16'), remainderCountNode()), null); + expectMaxSize(setTypeNode(integerTypeNode('u16'), prefixedCountNode(integerTypeNode('u8'))), null); + expectMaxSize(setTypeNode(integerTypeNode('u16'), remainderCountNode()), null); }); test('it returns null if the inner type has no max size', () => { expectMaxSize(setTypeNode(stringTypeNode('utf8'), fixedCountNode(3)), null); - expectMaxSize(setTypeNode(stringTypeNode('utf8'), prefixedCountNode(numberTypeNode('u8'))), null); + expectMaxSize(setTypeNode(stringTypeNode('utf8'), prefixedCountNode(integerTypeNode('u8'))), null); expectMaxSize(setTypeNode(stringTypeNode('utf8'), remainderCountNode()), null); }); test('it returns 0 if the inner type max size is 0 and the count is fixed', () => { @@ -487,27 +545,31 @@ describe('setTypeNode', () => { expectMaxSize(setTypeNode(tupleTypeNode([]), remainderCountNode()), 0); }); test('it returns the prefix max size if the inner type size is 0 and the count is prefixed', () => { - expectMaxSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('u32'))), 4); - expectMaxSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(numberTypeNode('shortU16'))), 3); + expectMaxSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('u32'))), 4); + expectMaxSize(setTypeNode(tupleTypeNode([]), prefixedCountNode(integerTypeNode('shortU16'))), 3); }); }); -describe('sizePrefixTypeNode', () => { +describe('sizePrefixTransformNode', () => { test('it returns the max size of the prefix if the inner type size is 0', () => { - expectMaxSize(sizePrefixTypeNode(tupleTypeNode([]), numberTypeNode('shortU16')), 3); + expectMaxSize( + addTypeNodeTransforms(tupleTypeNode([]), [sizePrefixTransformNode(integerTypeNode('shortU16'))]), + 3, + ); }); test('it returns the sum of the prefix and the inner type max sizes if they both exist', () => { - expectMaxSize(sizePrefixTypeNode(optionTypeNode(publicKeyTypeNode()), numberTypeNode('shortU16')), 3 + 33); + expectMaxSize( + addTypeNodeTransforms(optionTypeNode(publicKeyTypeNode()), [ + sizePrefixTransformNode(integerTypeNode('shortU16')), + ]), + 3 + 33, + ); }); test('it returns null if the inner type has no max size', () => { - expectMaxSize(sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')), null); - }); -}); - -describe('solAmountTypeNode', () => { - test('it delegates to the underlying number type', () => { - expectMaxSize(solAmountTypeNode(numberTypeNode('u64')), 8); - expectMaxSize(solAmountTypeNode(numberTypeNode('shortU16')), 3); + expectMaxSize( + addTypeNodeTransforms(stringTypeNode('utf8'), [sizePrefixTransformNode(integerTypeNode('u32'))]), + null, + ); }); }); @@ -522,8 +584,8 @@ describe('stringTypeNode', () => { describe('structFieldTypeNode', () => { test('it returns the max size of the inner type', () => { - expectMaxSize(structFieldTypeNode({ name: 'withMaxSize', type: numberTypeNode('shortU16') }), 3); - expectMaxSize(structFieldTypeNode({ name: 'withoutMaxSize', type: stringTypeNode('utf8') }), null); + expectMaxSize(structFieldTypeNode({ identifier: 'withMaxSize', type: integerTypeNode('shortU16') }), 3); + expectMaxSize(structFieldTypeNode({ identifier: 'withoutMaxSize', type: stringTypeNode('utf8') }), null); }); }); @@ -531,10 +593,10 @@ describe('structTypeNode', () => { test('it returns the sum of the field max sizes if they all have one', () => { expectMaxSize( structTypeNode([ - structFieldTypeNode({ name: 'age', type: numberTypeNode('shortU16') }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('shortU16') }), structFieldTypeNode({ - name: 'firstname', - type: optionTypeNode(fixedSizeTypeNode(stringTypeNode('utf8'), 42)), + identifier: 'firstname', + type: optionTypeNode(addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(42)])), }), ]), 3 + 43, @@ -543,8 +605,8 @@ describe('structTypeNode', () => { test('it returns null if any field has no max size', () => { expectMaxSize( structTypeNode([ - structFieldTypeNode({ name: 'age', type: numberTypeNode('u32') }), - structFieldTypeNode({ name: 'firstname', type: stringTypeNode('utf8') }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u32') }), + structFieldTypeNode({ identifier: 'firstname', type: stringTypeNode('utf8') }), ]), null, ); @@ -553,23 +615,23 @@ describe('structTypeNode', () => { describe('tupleTypeNode', () => { test('it returns the sum of all max sizes if all items have one', () => { - expectMaxSize(tupleTypeNode([numberTypeNode('shortU16'), optionTypeNode(numberTypeNode('u32'))]), 3 + 5); + expectMaxSize(tupleTypeNode([integerTypeNode('shortU16'), optionTypeNode(integerTypeNode('u32'))]), 3 + 5); }); test('it returns null if any item has no max size', () => { - expectMaxSize(tupleTypeNode([numberTypeNode('u16'), stringTypeNode('utf8')]), null); + expectMaxSize(tupleTypeNode([integerTypeNode('u16'), stringTypeNode('utf8')]), null); }); }); describe('zeroableOptionTypeNode', () => { test('it returns the inner item max size if is has one', () => { - expectMaxSize(zeroableOptionTypeNode(numberTypeNode('shortU16')), 3); + expectMaxSize(zeroableOptionTypeNode(integerTypeNode('shortU16')), 3); }); test('it returns null if the inner item has no max size', () => { expectMaxSize(zeroableOptionTypeNode(stringTypeNode('utf8')), null); }); test('it returns the maximum value between the inner item max size and the zero value when provided', () => { const zeroValue = (bytes: number) => constantValueNodeFromString('base16', 'ff'.repeat(bytes)); - expectMaxSize(zeroableOptionTypeNode(numberTypeNode('u32'), zeroValue(2)), 4); - expectMaxSize(zeroableOptionTypeNode(numberTypeNode('u32'), zeroValue(42)), 42); + expectMaxSize(zeroableOptionTypeNode(integerTypeNode('u32'), { zeroValue: zeroValue(2) }), 4); + expectMaxSize(zeroableOptionTypeNode(integerTypeNode('u32'), { zeroValue: zeroValue(42) }), 42); }); }); diff --git a/packages/visitors-core/test/getResolvedInstructionInputsVisitor.test.ts b/packages/visitors-core/test/getResolvedInstructionInputsVisitor.test.ts index b2f2bba45..ce469f963 100644 --- a/packages/visitors-core/test/getResolvedInstructionInputsVisitor.test.ts +++ b/packages/visitors-core/test/getResolvedInstructionInputsVisitor.test.ts @@ -1,52 +1,74 @@ +import { CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, CodamaError } from '@codama/errors'; import { + accountBumpValueNode, accountValueNode, + definedTypeLinkNode, + definedTypeNode, + identifierString, + injectedValueNode, + INSTRUCTION_INPUT_VALUE_NODES, + InstructionNode, instructionAccountNode, - instructionArgumentNode, instructionNode, - numberTypeNode, + integerTypeNode, pdaSeedValueNode, pdaValueNode, + programNode, + providedNode, publicKeyTypeNode, + publicKeyValueNode, + structFieldTypeNode, + structTypeNode, } from '@codama/nodes'; import { expect, test } from 'vitest'; -import { getResolvedInstructionInputsVisitor, visit } from '../src'; +import { + getRecordLinkablesVisitor, + getResolvedInstructionInputsVisitor, + LinkableDictionary, + NodeStack, + ProvidedScope, + visit, +} from '../src'; + +const resolve = (node: InstructionNode) => visit(node, getResolvedInstructionInputsVisitor(new LinkableDictionary())); test('it returns all instruction accounts in order of resolution', () => { - // Given the following instruction node with an account that defaults to another account. + // Given an instruction node with an account that defaults to another account. const node = instructionNode({ accounts: [ instructionAccountNode({ defaultValue: accountValueNode('authority'), + identifier: 'owner', isSigner: true, isWritable: false, - name: 'owner', }), instructionAccountNode({ + identifier: 'authority', isSigner: true, isWritable: false, - name: 'authority', }), ], - name: 'myInstruction', + identifier: 'myInstruction', }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); // Then we expect the accounts to be in order of resolution. expect(result).toEqual([ { - ...(node.accounts ?? [])[1], dependsOn: [], isPda: false, + node: (node.accounts ?? [])[1], resolvedIsOptional: false, resolvedIsSigner: true, }, { - ...(node.accounts ?? [])[0], dependsOn: [accountValueNode('authority')], isPda: false, + node: (node.accounts ?? [])[0], + resolvedDefaultValue: accountValueNode('authority'), resolvedIsOptional: false, resolvedIsSigner: true, }, @@ -54,254 +76,487 @@ test('it returns all instruction accounts in order of resolution', () => { }); test('it sets the resolved signer to either when a non signer defaults to a signer account', () => { - // Given the following instruction node such that a non signer account defaults to a signer account. + // Given an instruction node such that a non signer account defaults to a signer account. const node = instructionNode({ accounts: [ instructionAccountNode({ defaultValue: accountValueNode('authority'), + identifier: 'owner', isSigner: false, isWritable: false, - name: 'owner', }), instructionAccountNode({ + identifier: 'authority', isSigner: true, isWritable: false, - name: 'authority', }), ], - name: 'myInstruction', + identifier: 'myInstruction', }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); // Then we expect the resolved signer to be either for the non signer account. expect(result[1]).toEqual({ - ...(node.accounts ?? [])[0], dependsOn: [accountValueNode('authority')], isPda: false, + node: (node.accounts ?? [])[0], + resolvedDefaultValue: accountValueNode('authority'), resolvedIsOptional: false, resolvedIsSigner: 'either', }); }); test('it sets the resolved signer to either when a signer defaults to a non signer account', () => { - // Given the following instruction node such that a signer account defaults to a non signer account. + // Given an instruction node such that a signer account defaults to a non signer account. const node = instructionNode({ accounts: [ instructionAccountNode({ defaultValue: accountValueNode('authority'), + identifier: 'owner', isSigner: true, isWritable: false, - name: 'owner', }), instructionAccountNode({ + identifier: 'authority', isSigner: false, isWritable: false, - name: 'authority', }), ], - name: 'myInstruction', + identifier: 'myInstruction', }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); // Then we expect the resolved signer to be either for the signer account. expect(result[1]).toEqual({ - ...(node.accounts ?? [])[0], dependsOn: [accountValueNode('authority')], isPda: false, + node: (node.accounts ?? [])[0], + resolvedDefaultValue: accountValueNode('authority'), resolvedIsOptional: false, resolvedIsSigner: 'either', }); }); -test('it includes instruction data arguments with default values', () => { - // Given the following instruction node with two arguments such that: - // - The first argument defaults to an account. - // - The second argument has no default value. +test('it includes instruction data fields with resolvable default values', () => { + // Given an instruction whose data has two fields such that: + // - The first field injects a value the instruction provides as an account reference. + // - The second field has no default value. const node = instructionNode({ accounts: [ instructionAccountNode({ + identifier: 'owner', isSigner: true, isWritable: false, - name: 'owner', }), ], - arguments: [ - instructionArgumentNode({ - defaultValue: accountValueNode('owner'), - name: 'ownerArg', + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'owner' }), + identifier: 'ownerArg', type: publicKeyTypeNode(), }), - instructionArgumentNode({ - name: 'argWithoutDefaults', - type: numberTypeNode('u8'), + structFieldTypeNode({ + identifier: 'argWithoutDefaults', + type: integerTypeNode('u8'), }), - ], - name: 'myInstruction', + ]), + identifier: 'myInstruction', + provides: [providedNode('owner', accountValueNode('owner'))], }); + const fields = (node.data as ReturnType).fields ?? []; // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); // Then we expect the following inputs. expect(result).toEqual([ { - ...(node.accounts ?? [])[0], dependsOn: [], isPda: false, + node: (node.accounts ?? [])[0], resolvedIsOptional: false, resolvedIsSigner: true, }, { - ...(node.arguments ?? [])[0], dependsOn: [accountValueNode('owner')], + node: fields[0], + path: 'ownerArg', + resolvedDefaultValue: accountValueNode('owner'), }, ]); - // And the argument without default value is not included. - expect(result.some(input => input.name === 'argWithoutDefaults')).toBe(false); + // And the field without a default value is not included. + expect(result.some(input => 'path' in input && input.path === 'argWithoutDefaults')).toBe(false); }); -test('it includes instruction extra arguments with default values', () => { - // Given the following instruction node with two extra arguments such that: - // - The first argument defaults to an account. - // - The second argument has no default value. +test('it returns an empty array for empty instructions', () => { + // Given an empty instruction node. + const node = instructionNode({ identifier: 'myInstruction' }); + + // When we get its resolved inputs. + const result = resolve(node); + + // Then we expect an empty array. + expect(result).toEqual([]); +}); + +test('it resolves the seeds of a PdaValueNode first', () => { + // Given an instruction node with an account that defaults to a PDA whose seed is another account. const node = instructionNode({ accounts: [ instructionAccountNode({ - isSigner: true, + defaultValue: pdaValueNode('counter', { + seeds: [pdaSeedValueNode('authority', accountValueNode('payer'))], + }), + identifier: 'counter', + isSigner: false, isWritable: false, - name: 'owner', - }), - ], - extraArguments: [ - instructionArgumentNode({ - defaultValue: accountValueNode('owner'), - name: 'ownerArg', - type: publicKeyTypeNode(), }), - instructionArgumentNode({ - name: 'argWithoutDefaults', - type: numberTypeNode('u8'), + instructionAccountNode({ + identifier: 'payer', + isSigner: true, + isWritable: false, }), ], - name: 'myInstruction', + identifier: 'myInstruction', }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); - // Then we expect the following inputs. + // Then we expect the accounts to be in order of resolution. expect(result).toEqual([ { - ...(node.accounts ?? [])[0], dependsOn: [], isPda: false, + node: (node.accounts ?? [])[1], resolvedIsOptional: false, resolvedIsSigner: true, }, { - ...node.extraArguments![0], - dependsOn: [accountValueNode('owner')], + dependsOn: [accountValueNode('payer')], + isPda: false, + node: (node.accounts ?? [])[0], + resolvedDefaultValue: pdaValueNode('counter', { + seeds: [pdaSeedValueNode('authority', accountValueNode('payer'))], + }), + resolvedIsOptional: false, + resolvedIsSigner: false, }, ]); - - // And the argument without default value is not included. - expect(result.some(input => input.name === 'argWithoutDefaults')).toBe(false); }); -test('it returns an empty array for empty instructions', () => { - // Given the following empty instruction node. - const node = instructionNode({ name: 'myInstruction' }); - - // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); - - // Then we expect an empty array. - expect(result).toEqual([]); -}); - -test('it resolves the seeds of a PdaValueNode first', () => { - // Given the following instruction node with an account that defaults to another account. +test('it resolves the program id of a PdaValueNode first', () => { + // Given an instruction node with an account that defaults to a PDA whose program id is another account. const node = instructionNode({ accounts: [ instructionAccountNode({ - defaultValue: pdaValueNode('counter', [pdaSeedValueNode('authority', accountValueNode('payer'))]), + defaultValue: pdaValueNode('counter', { programId: accountValueNode('counterProgram') }), + identifier: 'counter', isSigner: false, isWritable: false, - name: 'counter', }), instructionAccountNode({ - isSigner: true, + identifier: 'counterProgram', + isSigner: false, isWritable: false, - name: 'payer', }), ], - name: 'myInstruction', + identifier: 'myInstruction', }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); // Then we expect the accounts to be in order of resolution. expect(result).toEqual([ { - ...(node.accounts ?? [])[1], dependsOn: [], isPda: false, + node: (node.accounts ?? [])[1], resolvedIsOptional: false, - resolvedIsSigner: true, + resolvedIsSigner: false, }, { - ...(node.accounts ?? [])[0], - dependsOn: [accountValueNode('payer')], + dependsOn: [accountValueNode('counterProgram')], isPda: false, + node: (node.accounts ?? [])[0], + resolvedDefaultValue: pdaValueNode('counter', { programId: accountValueNode('counterProgram') }), resolvedIsOptional: false, resolvedIsSigner: false, }, ]); }); -test('it resolves the program id of a PdaValueNode first', () => { - // Given the following instruction node with an account that defaults to another account. +test('it marks an account as a PDA when a data field resolves to its bump', () => { + // Given an instruction whose data injects the bump of one of its accounts, + // fulfilled by the instruction's own provides. const node = instructionNode({ accounts: [ instructionAccountNode({ - defaultValue: pdaValueNode('counter', [], accountValueNode('counterProgram')), + identifier: 'counter', isSigner: false, - isWritable: false, - name: 'counter', + isWritable: true, }), + ], + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'counterBump' }), + identifier: 'counterBump', + type: integerTypeNode('u8'), + }), + ]), + identifier: 'myInstruction', + provides: [providedNode('counterBump', accountBumpValueNode('counter'))], + }); + + // When we get its resolved inputs. + const result = resolve(node); + + // Then the counter account is marked as a PDA. + const counter = result.find(input => input.node.kind === 'instructionAccountNode'); + expect(counter).toMatchObject({ isPda: true }); +}); + +test('it throws when a provided value is not an instruction input value node', () => { + // Given an instruction that provides a non-value node for an injected data-field default. + const provider = providedNode('bogus', publicKeyTypeNode()); + const node = instructionNode({ + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'bogus' }), + identifier: 'field', + type: integerTypeNode('u8'), + }), + ]), + identifier: 'myInstruction', + provides: [provider], + }); + + // When we get its resolved inputs, then it throws. + expect(() => resolve(node)).toThrow( + new CodamaError(CODAMA_ERROR__VISITORS__INVALID_PROVIDED_VALUE, { + expectedKinds: INSTRUCTION_INPUT_VALUE_NODES, + key: identifierString('bogus'), + providedKind: 'publicKeyTypeNode', + provider, + }), + ); +}); + +test('it resolves injections nested within a default value', () => { + // Given an account defaulting to a PDA whose seed is injected. + const node = instructionNode({ + accounts: [ instructionAccountNode({ + defaultValue: pdaValueNode('counter', { + seeds: [pdaSeedValueNode('authority', injectedValueNode({ key: 'counterAuthority' }))], + }), + identifier: 'counter', isSigner: false, - isWritable: false, - name: 'counterProgram', + isWritable: true, }), + instructionAccountNode({ identifier: 'payer', isSigner: true, isWritable: false }), ], - name: 'myInstruction', + identifier: 'myInstruction', + provides: [providedNode('counterAuthority', accountValueNode('payer'))], }); // When we get its resolved inputs. - const result = visit(node, getResolvedInstructionInputsVisitor()); + const result = resolve(node); - // Then we expect the accounts to be in order of resolution. + // Then the seed is resolved, and the account it refers to is resolved first. + expect(result.map(input => input.node.identifier)).toEqual(['payer', 'counter']); + expect(result[1]).toMatchObject({ + dependsOn: [accountValueNode('payer')], + resolvedDefaultValue: pdaValueNode('counter', { + seeds: [pdaSeedValueNode('authority', accountValueNode('payer'))], + }), + }); +}); + +test('it drops a default value with an unresolvable nested injection', () => { + // Given an account defaulting to a PDA whose injected seed nothing provides. + const node = instructionNode({ + accounts: [ + instructionAccountNode({ + defaultValue: pdaValueNode('counter', { + seeds: [pdaSeedValueNode('authority', injectedValueNode({ key: 'counterAuthority' }))], + }), + identifier: 'counter', + isSigner: false, + isWritable: true, + }), + ], + identifier: 'myInstruction', + }); + + // When we get its resolved inputs. + const result = resolve(node); + + // Then the account has no resolved default rather than a PDA missing a seed. expect(result).toEqual([ { - ...(node.accounts ?? [])[1], dependsOn: [], isPda: false, - resolvedIsOptional: false, - resolvedIsSigner: false, - }, - { - ...(node.accounts ?? [])[0], - dependsOn: [accountValueNode('counterProgram')], - isPda: false, + node: (node.accounts ?? [])[0], resolvedIsOptional: false, resolvedIsSigner: false, }, ]); }); + +test('it resolves injections against the parent instructions of a caller-supplied scope', () => { + // Given a sub-instruction whose data injects a key provided by its parent. + const subInstruction = instructionNode({ + accounts: [instructionAccountNode({ identifier: 'counter', isSigner: false, isWritable: true })], + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'counterBump' }), + identifier: 'bump', + type: integerTypeNode('u8'), + }), + ]), + identifier: 'subInstruction', + }); + + // And a scope already holding the parent instruction's frame. + const scope = new ProvidedScope([providedNode('counterBump', accountBumpValueNode('counter'))]); + + // When we get its resolved inputs using that scope. + const result = visit(subInstruction, getResolvedInstructionInputsVisitor(new LinkableDictionary(), { scope })); + + // Then the field resolves through the parent's provider. + const field = result.find(input => 'path' in input && input.path === 'bump'); + expect(field).toMatchObject({ resolvedDefaultValue: accountBumpValueNode('counter') }); +}); + +test('it lets an instruction shadow a key provided by its parent', () => { + // Given an instruction providing a key its parent also provides. + const node = instructionNode({ + accounts: [ + instructionAccountNode({ identifier: 'inner', isSigner: false, isWritable: false }), + instructionAccountNode({ identifier: 'outer', isSigner: false, isWritable: false }), + ], + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'target' }), + identifier: 'target', + type: publicKeyTypeNode(), + }), + ]), + identifier: 'myInstruction', + provides: [providedNode('target', accountValueNode('inner'))], + }); + const scope = new ProvidedScope([providedNode('target', accountValueNode('outer'))]); + + // When we get its resolved inputs. + const result = visit(node, getResolvedInstructionInputsVisitor(new LinkableDictionary(), { scope })); + + // Then the instruction's own provider wins. + const field = result.find(input => 'path' in input && input.path === 'target'); + expect(field).toMatchObject({ dependsOn: [accountValueNode('inner')] }); +}); + +test('it collects data fields through a defined type link', () => { + // Given an instruction whose data links to a program-level struct type. + const instruction = instructionNode({ + accounts: [instructionAccountNode({ identifier: 'counter', isSigner: false, isWritable: true })], + data: definedTypeLinkNode('counterArgs'), + identifier: 'increment', + provides: [providedNode('counterBump', accountBumpValueNode('counter'))], + }); + const program = programNode({ + definedTypes: [ + definedTypeNode({ + identifier: 'counterArgs', + type: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'counterBump' }), + identifier: 'bump', + type: integerTypeNode('u8'), + }), + ]), + }), + ], + identifier: 'counterProgram', + instructions: [instruction], + publicKey: '1111', + }); + const linkables = new LinkableDictionary(); + visit(program, getRecordLinkablesVisitor(linkables)); + + // When we get its resolved inputs from within its program. + const stack = new NodeStack([program]); + const result = visit(instruction, getResolvedInstructionInputsVisitor(linkables, { stack })); + + // Then the linked struct's fields are resolved, and the bump marks the account as a PDA. + expect(result.find(input => 'path' in input && input.path === 'bump')).toMatchObject({ + resolvedDefaultValue: accountBumpValueNode('counter'), + }); + expect(result.find(input => input.node.kind === 'instructionAccountNode')).toMatchObject({ isPda: true }); +}); + +test('it resolves an injected data-field default through the instruction provides', () => { + // Given an instruction that injects a value fulfilled by its own provides. + const node = instructionNode({ + accounts: [ + instructionAccountNode({ + identifier: 'counter', + isSigner: false, + isWritable: true, + }), + ], + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ key: 'counterBump' }), + identifier: 'bump', + type: integerTypeNode('u8'), + }), + ]), + identifier: 'myInstruction', + provides: [providedNode('counterBump', accountBumpValueNode('counter'))], + }); + + // When we get its resolved inputs. + const result = resolve(node); + + // Then the field's resolved default is the provided contextual value. + const field = result.find(input => 'path' in input && input.path === 'bump'); + expect(field).toMatchObject({ + dependsOn: [accountValueNode('counter')], + resolvedDefaultValue: accountBumpValueNode('counter'), + }); +}); + +test('it falls back to the injected value fallback when no provider is found', () => { + // Given an instruction whose injected data-field default has no matching provider but a static fallback. + const fallback = publicKeyValueNode('11111111111111111111111111111111'); + const node = instructionNode({ + data: structTypeNode([ + structFieldTypeNode({ + defaultValue: injectedValueNode({ fallback, key: 'missing' }), + identifier: 'signer', + type: publicKeyTypeNode(), + }), + ]), + identifier: 'myInstruction', + }); + + // When we get its resolved inputs, including fields with static defaults. + const result = visit( + node, + getResolvedInstructionInputsVisitor(new LinkableDictionary(), { includeDataValueNodes: true }), + ); + + // Then the field resolves to the static fallback with no dependencies. + const field = result.find(input => 'path' in input && input.path === 'signer'); + expect(field).toMatchObject({ + dependsOn: [], + resolvedDefaultValue: fallback, + }); +}); diff --git a/packages/visitors-core/test/getUniqueHashStringVisitor.test.ts b/packages/visitors-core/test/getUniqueHashStringVisitor.test.ts index ea7ad434b..45ec102fd 100644 --- a/packages/visitors-core/test/getUniqueHashStringVisitor.test.ts +++ b/packages/visitors-core/test/getUniqueHashStringVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { getUniqueHashStringVisitor, visit } from '../src'; test('it returns a unique string representing the whole node', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // When we get its unique hash string. const result = visit(node, getUniqueHashStringVisitor()); @@ -13,8 +13,8 @@ test('it returns a unique string representing the whole node', () => { // Then we expect the following string. expect(result).toEqual( '{"items":[' + - '{"endian":"le","format":"u32","kind":"numberTypeNode"},' + - '{"items":[{"endian":"le","format":"u32","kind":"numberTypeNode"},{"kind":"publicKeyTypeNode"}],"kind":"tupleTypeNode"}' + + '{"endian":"le","format":"u32","kind":"integerTypeNode"},' + + '{"items":[{"endian":"le","format":"u32","kind":"integerTypeNode"},{"kind":"publicKeyTypeNode"}],"kind":"tupleTypeNode"}' + '],"kind":"tupleTypeNode"}', ); }); @@ -23,8 +23,8 @@ test('it returns a unique string whilst discard docs', () => { // Given the following tree with docs. const node = structTypeNode([ structFieldTypeNode({ - docs: ['The owner of the account.'], - name: 'owner', + docs: 'The owner of the account.', + identifier: 'owner', type: publicKeyTypeNode(), }), ]); @@ -38,7 +38,7 @@ test('it returns a unique string whilst discard docs', () => { // omission on the rendered hash. expect(result).toEqual( '{"fields":[' + - '{"kind":"structFieldTypeNode","name":"owner","type":{"kind":"publicKeyTypeNode"}}' + + '{"identifier":"owner","kind":"structFieldTypeNode","type":{"kind":"publicKeyTypeNode"}}' + '],"kind":"structTypeNode"}', ); }); diff --git a/packages/visitors-core/test/identityVisitor.test.ts b/packages/visitors-core/test/identityVisitor.test.ts index cb9d2867c..18dcb4e03 100644 --- a/packages/visitors-core/test/identityVisitor.test.ts +++ b/packages/visitors-core/test/identityVisitor.test.ts @@ -1,11 +1,11 @@ -import { assertIsNode, numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { assertIsNode, integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { identityVisitor, interceptVisitor, visit } from '../src'; test('it visits all nodes and returns different instances of the same nodes', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // When we visit it using the identity visitor. const result = visit(node, identityVisitor()); @@ -22,7 +22,7 @@ test('it visits all nodes and returns different instances of the same nodes', () test('it can remove nodes by returning null', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And given an identity visitor overidden to remove all public key nodes. const visitor = identityVisitor(); @@ -32,17 +32,17 @@ test('it can remove nodes by returning null', () => { const result = visit(node, visitor); // Then we expect the following tree back. - expect(result).toEqual(tupleTypeNode([numberTypeNode('u32')])); + expect(result).toEqual(tupleTypeNode([integerTypeNode('u32')])); }); test('it can create partial visitors', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And an identity visitor that only supports 2 of these nodes // whilst using an interceptor to record the events that happened. const events: string[] = []; - const visitor = interceptVisitor(identityVisitor({ keys: ['tupleTypeNode', 'numberTypeNode'] }), (node, next) => { + const visitor = interceptVisitor(identityVisitor({ keys: ['tupleTypeNode', 'integerTypeNode'] }), (node, next) => { events.push(`visiting:${node.kind}`); return next(node); }); @@ -58,7 +58,7 @@ test('it can create partial visitors', () => { expect((result.items ?? [])[1]).not.toBe((node.items ?? [])[1]); // But the unsupported node was not visited. - expect(events).toEqual(['visiting:tupleTypeNode', 'visiting:numberTypeNode']); + expect(events).toEqual(['visiting:tupleTypeNode', 'visiting:integerTypeNode']); // And the unsupported node cannot be visited. // @ts-expect-error PublicKeyTypeNode is not supported. diff --git a/packages/visitors-core/test/interceptFirstVisitVisitor.test.ts b/packages/visitors-core/test/interceptFirstVisitVisitor.test.ts index 27de14973..d2750236f 100644 --- a/packages/visitors-core/test/interceptFirstVisitVisitor.test.ts +++ b/packages/visitors-core/test/interceptFirstVisitVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { extendVisitor, interceptFirstVisitVisitor, visit, voidVisitor } from '../src'; test('it returns a new visitor that only intercepts the first visit of a visitor', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And an intercepted void visitor that records the events that happened during the first visit. const events: string[] = []; @@ -28,7 +28,7 @@ test('it returns a new visitor that only intercepts the first visit of a visitor test('it still works on subsequent calls', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And an intercepted void visitor that records the events that happened during the first visit. const events: string[] = []; @@ -48,7 +48,7 @@ test('it still works on subsequent calls', () => { test('it resets the first visit boolean if an error is thrown', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And an intercepted visitor that records the events that happened during the first visit // but throws an error when it visits publicKeyTypeNodes. diff --git a/packages/visitors-core/test/interceptVisitor.test.ts b/packages/visitors-core/test/interceptVisitor.test.ts index 9f0f11fdd..fca66f48e 100644 --- a/packages/visitors-core/test/interceptVisitor.test.ts +++ b/packages/visitors-core/test/interceptVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { interceptVisitor, visit, voidVisitor } from '../src'; test('it returns a new visitor that intercepts all visits of a visitor', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And an intercepted void visitor that records the events that happened during each visit. const events: string[] = []; @@ -22,8 +22,8 @@ test('it returns a new visitor that intercepts all visits of a visitor', () => { // Then we expect the following events to have happened. expect(events).toEqual([ 'down:tupleTypeNode', - 'down:numberTypeNode', - 'up:numberTypeNode', + 'down:integerTypeNode', + 'up:integerTypeNode', 'down:publicKeyTypeNode', 'up:publicKeyTypeNode', 'up:tupleTypeNode', diff --git a/packages/visitors-core/test/mapVisitor.test.ts b/packages/visitors-core/test/mapVisitor.test.ts index a07cbcaa6..ccecb153a 100644 --- a/packages/visitors-core/test/mapVisitor.test.ts +++ b/packages/visitors-core/test/mapVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { mapVisitor, mergeVisitor, staticVisitor, visit, Visitor } from '../src'; test('it maps the return value of a visitor to another', () => { // Given the following 3-nodes tree. - const item0 = numberTypeNode('u32'); + const item0 = integerTypeNode('u32'); const item1 = publicKeyTypeNode(); const node = tupleTypeNode([item0, item1]); @@ -19,24 +19,24 @@ test('it maps the return value of a visitor to another', () => { const visitorB = mapVisitor(visitorA, value => value.length); // Then we expect the following results when visiting different nodes. - expect(visit(node, visitorB)).toBe(49); - expect(visit(item0, visitorB)).toBe(16); - expect(visit(item1, visitorB)).toBe(17); + expect(visit(node, visitorB)).toBe(52); + expect(visit(item0, visitorB)).toBe(17); + expect(visit(item1, visitorB)).toBe(19); }); test('it creates partial visitors from partial visitors', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And partial static visitor A that supports only 2 of these nodes. - const visitorA = staticVisitor(node => node.kind, { keys: ['tupleTypeNode', 'numberTypeNode'] }); + const visitorA = staticVisitor(node => node.kind, { keys: ['tupleTypeNode', 'integerTypeNode'] }); // And a mapped visitor B that returns the number of characters returned by visitor A. const visitorB = mapVisitor(visitorA, value => value.length); // Then both visitors are partial. - visitorA satisfies Visitor; - visitorB satisfies Visitor; + visitorA satisfies Visitor; + visitorB satisfies Visitor; // Then we expect an error when visiting an unsupported node. // @ts-expect-error PublicKeyTypeNode is not supported. diff --git a/packages/visitors-core/test/mergeVisitor.test.ts b/packages/visitors-core/test/mergeVisitor.test.ts index de698f3d2..a8d549749 100644 --- a/packages/visitors-core/test/mergeVisitor.test.ts +++ b/packages/visitors-core/test/mergeVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { mergeVisitor, visit } from '../src'; test('it sets a value for all leaves and merges node values together', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And a visitor that sets the node kind for all leaves and combines // them together such that each node lists the kind of its children. @@ -18,12 +18,12 @@ test('it sets a value for all leaves and merges node values together', () => { const result = visit(node, visitor); // Then we get the following result. - expect(result).toBe('tupleTypeNode(numberTypeNode(),publicKeyTypeNode)'); + expect(result).toBe('tupleTypeNode(integerTypeNode(),publicKeyTypeNode())'); }); test('it can be used to count nodes', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // When we visit the tree with a visitor that counts nodes. const visitor = mergeVisitor( @@ -38,20 +38,20 @@ test('it can be used to count nodes', () => { test('it can create partial visitors', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And a visitor that only supports 2 of these nodes. const visitor = mergeVisitor( node => node.kind as string, (node, values) => `${node.kind}(${values.join(',')})`, - { keys: ['tupleTypeNode', 'numberTypeNode'] }, + { keys: ['tupleTypeNode', 'integerTypeNode'] }, ); // When we visit the tree using that visitor. const result = visit(node, visitor); // Then the unsupported node is not included in the result. - expect(result).toBe('tupleTypeNode(numberTypeNode())'); + expect(result).toBe('tupleTypeNode(integerTypeNode())'); // And the unsupported node cannot be visited. // @ts-expect-error PublicKeyTypeNode is not supported. diff --git a/packages/visitors-core/test/nodes/AccountNode.test.ts b/packages/visitors-core/test/nodes/AccountNode.test.ts index a8e72ad6f..3228e840d 100644 --- a/packages/visitors-core/test/nodes/AccountNode.test.ts +++ b/packages/visitors-core/test/nodes/AccountNode.test.ts @@ -1,6 +1,6 @@ import { accountNode, - numberTypeNode, + integerTypeNode, pdaLinkNode, publicKeyTypeNode, sizeDiscriminatorNode, @@ -18,12 +18,12 @@ import { const node = accountNode({ data: structTypeNode([ - structFieldTypeNode({ name: 'mint', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ identifier: 'mint', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }), ]), discriminators: [sizeDiscriminatorNode(72)], - name: 'token', + identifier: 'token', pda: pdaLinkNode('associatedToken'), size: 72, }); @@ -52,7 +52,7 @@ accountNode [token] | | structFieldTypeNode [owner] | | | publicKeyTypeNode | | structFieldTypeNode [amount] -| | | numberTypeNode [u64] +| | | integerTypeNode [u64] | pdaLinkNode [associatedToken] | sizeDiscriminatorNode [72]`, ); diff --git a/packages/visitors-core/test/nodes/ConstantNode.test.ts b/packages/visitors-core/test/nodes/ConstantNode.test.ts index 18b2cb474..bd90c11ed 100644 --- a/packages/visitors-core/test/nodes/ConstantNode.test.ts +++ b/packages/visitors-core/test/nodes/ConstantNode.test.ts @@ -1,4 +1,4 @@ -import { constantNode, numberTypeNode, numberValueNode } from '@codama/nodes'; +import { constantNode, integerTypeNode, integerValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from './_setup'; -const node = constantNode('maxItems', numberTypeNode('u64'), numberValueNode(1000)); +const node = constantNode('maxItems', integerTypeNode('u64'), integerValueNode('1000')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 3); @@ -20,8 +20,8 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[constantNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberValueNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerValueNode]', null); }); test('debugStringVisitor', () => { @@ -29,7 +29,7 @@ test('debugStringVisitor', () => { node, ` constantNode [maxItems] -| numberTypeNode [u64] -| numberValueNode [1000]`, +| integerTypeNode [u64] +| integerValueNode [1000]`, ); }); diff --git a/packages/visitors-core/test/nodes/DefinedTypeNode.test.ts b/packages/visitors-core/test/nodes/DefinedTypeNode.test.ts index 57989ad9c..f0370ebfc 100644 --- a/packages/visitors-core/test/nodes/DefinedTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/DefinedTypeNode.test.ts @@ -1,7 +1,8 @@ import { + addTypeNodeTransforms, definedTypeNode, - fixedSizeTypeNode, - numberTypeNode, + fixedSizeTransformNode, + integerTypeNode, stringTypeNode, structFieldTypeNode, structTypeNode, @@ -16,13 +17,13 @@ import { } from './_setup'; const node = definedTypeNode({ - name: 'person', + identifier: 'person', type: structTypeNode([ structFieldTypeNode({ - name: 'name', - type: fixedSizeTypeNode(stringTypeNode('utf8'), 42), + identifier: 'name', + type: addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(42)]), }), - structFieldTypeNode({ name: 'age', type: numberTypeNode('u64') }), + structFieldTypeNode({ identifier: 'age', type: integerTypeNode('u64') }), ]), }); @@ -46,9 +47,9 @@ test('debugStringVisitor', () => { definedTypeNode [person] | structTypeNode | | structFieldTypeNode [name] -| | | fixedSizeTypeNode [42] -| | | | stringTypeNode [utf8] +| | | stringTypeNode [utf8] +| | | | fixedSizeTransformNode [42] | | structFieldTypeNode [age] -| | | numberTypeNode [u64]`, +| | | integerTypeNode [u64]`, ); }); diff --git a/packages/visitors-core/test/nodes/ErrorNode.test.ts b/packages/visitors-core/test/nodes/ErrorNode.test.ts index 718151f68..7ced5f220 100644 --- a/packages/visitors-core/test/nodes/ErrorNode.test.ts +++ b/packages/visitors-core/test/nodes/ErrorNode.test.ts @@ -10,8 +10,8 @@ import { const node = errorNode({ code: 42, + identifier: 'InvalidTokenOwner', message: 'The provided account does not match the owner of the token account.', - name: 'InvalidTokenOwner', }); test('mergeVisitor', () => { @@ -27,5 +27,5 @@ test('deleteNodesVisitor', () => { }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `errorNode [42.invalidTokenOwner]`); + expectDebugStringVisitor(node, `errorNode [42.InvalidTokenOwner]`); }); diff --git a/packages/visitors-core/test/nodes/EventNode.test.ts b/packages/visitors-core/test/nodes/EventNode.test.ts index 23d5e191c..c908d090b 100644 --- a/packages/visitors-core/test/nodes/EventNode.test.ts +++ b/packages/visitors-core/test/nodes/EventNode.test.ts @@ -1,6 +1,6 @@ import { eventNode, - numberTypeNode, + integerTypeNode, publicKeyTypeNode, sizeDiscriminatorNode, structFieldTypeNode, @@ -17,11 +17,11 @@ import { const node = eventNode({ data: structTypeNode([ - structFieldTypeNode({ name: 'authority', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ identifier: 'authority', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }), ]), discriminators: [sizeDiscriminatorNode(40)], - name: 'transferEvent', + identifier: 'transferEvent', }); test('mergeVisitor', () => { @@ -45,7 +45,7 @@ eventNode [transferEvent] | | structFieldTypeNode [authority] | | | publicKeyTypeNode | | structFieldTypeNode [amount] -| | | numberTypeNode [u64] +| | | integerTypeNode [u64] | sizeDiscriminatorNode [40]`, ); }); diff --git a/packages/visitors-core/test/nodes/InstructionAccountNode.test.ts b/packages/visitors-core/test/nodes/InstructionAccountNode.test.ts index 67fb582fa..5971b19bc 100644 --- a/packages/visitors-core/test/nodes/InstructionAccountNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionAccountNode.test.ts @@ -10,10 +10,10 @@ import { const node = instructionAccountNode({ defaultValue: accountValueNode('authority'), + identifier: 'owner', isOptional: false, isSigner: 'either', isWritable: true, - name: 'owner', }); test('mergeVisitor', () => { diff --git a/packages/visitors-core/test/nodes/InstructionArgumentNode.test.ts b/packages/visitors-core/test/nodes/InstructionArgumentNode.test.ts deleted file mode 100644 index c2b973220..000000000 --- a/packages/visitors-core/test/nodes/InstructionArgumentNode.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { instructionArgumentNode, numberTypeNode, numberValueNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from './_setup'; - -const node = instructionArgumentNode({ - defaultValue: numberValueNode(1), - name: 'amount', - type: numberTypeNode('u64'), -}); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 3); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[instructionArgumentNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor( - node, - '[numberValueNode]', - instructionArgumentNode({ name: 'amount', type: numberTypeNode('u64') }), - ); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -instructionArgumentNode [amount] -| numberTypeNode [u64] -| numberValueNode [1]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/InstructionByteDeltaNode.test.ts b/packages/visitors-core/test/nodes/InstructionByteDeltaNode.test.ts index c3a51214b..512cc5b9f 100644 --- a/packages/visitors-core/test/nodes/InstructionByteDeltaNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionByteDeltaNode.test.ts @@ -1,4 +1,4 @@ -import { instructionByteDeltaNode, numberValueNode } from '@codama/nodes'; +import { instructionByteDeltaNode, integerValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from './_setup'; -const node = instructionByteDeltaNode(numberValueNode(42), { +const node = instructionByteDeltaNode(integerValueNode('42'), { subtract: true, }); @@ -22,7 +22,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[instructionByteDeltaNode]', null); - expectDeleteNodesVisitor(node, '[numberValueNode]', null); + expectDeleteNodesVisitor(node, '[integerValueNode]', null); }); test('debugStringVisitor', () => { @@ -30,6 +30,6 @@ test('debugStringVisitor', () => { node, ` instructionByteDeltaNode [subtract.withHeader] -| numberValueNode [42]`, +| integerValueNode [42]`, ); }); diff --git a/packages/visitors-core/test/nodes/InstructionNode.test.ts b/packages/visitors-core/test/nodes/InstructionNode.test.ts index cc5dc6481..58ad0b19d 100644 --- a/packages/visitors-core/test/nodes/InstructionNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionNode.test.ts @@ -1,15 +1,14 @@ import { fieldDiscriminatorNode, instructionAccountNode, - instructionArgumentNode, instructionByteDeltaNode, instructionNode, instructionRemainingAccountsNode, instructionStatusNode, - numberTypeNode, - numberValueNode, - publicKeyTypeNode, - resolverValueNode, + integerTypeNode, + integerValueNode, + structFieldTypeNode, + structTypeNode, } from '@codama/nodes'; import { test } from 'vitest'; @@ -23,32 +22,32 @@ import { const node = instructionNode({ accounts: [ instructionAccountNode({ + identifier: 'source', isSigner: true, isWritable: true, - name: 'source', }), instructionAccountNode({ + identifier: 'destination', isSigner: false, isWritable: true, - name: 'destination', }), ], - arguments: [ - instructionArgumentNode({ - name: 'discriminator', - type: numberTypeNode('u32'), + data: structTypeNode([ + structFieldTypeNode({ + identifier: 'discriminator', + type: integerTypeNode('u32'), }), - instructionArgumentNode({ - name: 'amount', - type: numberTypeNode('u64'), + structFieldTypeNode({ + identifier: 'amount', + type: integerTypeNode('u64'), }), - ], + ]), discriminators: [fieldDiscriminatorNode('discriminator')], - name: 'transferSol', + identifier: 'transferSol', }); test('mergeVisitor', () => { - expectMergeVisitorCount(node, 8); + expectMergeVisitorCount(node, 9); }); test('identityVisitor', () => { @@ -58,7 +57,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[instructionNode]', null); expectDeleteNodesVisitor(node, '[instructionAccountNode]', { ...node, accounts: undefined }); - expectDeleteNodesVisitor(node, '[instructionArgumentNode]', { ...node, arguments: undefined }); + expectDeleteNodesVisitor(node, '[structTypeNode]', { ...node, data: undefined }); expectDeleteNodesVisitor(node, '[fieldDiscriminatorNode]', { ...node, discriminators: undefined }); }); @@ -69,43 +68,31 @@ test('debugStringVisitor', () => { instructionNode [transferSol] | instructionAccountNode [source.writable.signer] | instructionAccountNode [destination.writable] -| instructionArgumentNode [discriminator] -| | numberTypeNode [u32] -| instructionArgumentNode [amount] -| | numberTypeNode [u64] +| structTypeNode +| | structFieldTypeNode [discriminator] +| | | integerTypeNode [u32] +| | structFieldTypeNode [amount] +| | | integerTypeNode [u64] | fieldDiscriminatorNode [discriminator]`, ); }); -test('extra arguments', () => { - const nodeWithExtraArguments = instructionNode({ - extraArguments: [ - instructionArgumentNode({ - name: 'myExtraArgument', - type: publicKeyTypeNode(), - }), - ], - name: 'myInstruction', - }); - - expectMergeVisitorCount(nodeWithExtraArguments, 3); - expectIdentityVisitor(nodeWithExtraArguments); -}); - test('remaining accounts', () => { const nodeWithRemainingAccounts = instructionNode({ - name: 'myInstruction', - remainingAccounts: [instructionRemainingAccountsNode(resolverValueNode('myResolver'))], + identifier: 'myInstruction', + remainingAccounts: [ + instructionRemainingAccountsNode('remainingAccounts', { isSigner: 'either', isWritable: true }), + ], }); - expectMergeVisitorCount(nodeWithRemainingAccounts, 3); + expectMergeVisitorCount(nodeWithRemainingAccounts, 2); expectIdentityVisitor(nodeWithRemainingAccounts); }); test('byte deltas', () => { const nodeWithByteDeltas = instructionNode({ - byteDeltas: [instructionByteDeltaNode(numberValueNode(42))], - name: 'myInstruction', + byteDeltas: [instructionByteDeltaNode(integerValueNode('42'))], + identifier: 'myInstruction', }); expectMergeVisitorCount(nodeWithByteDeltas, 3); @@ -114,10 +101,10 @@ test('byte deltas', () => { test('sub instructions', () => { const nodeWithSubInstructions = instructionNode({ - name: 'myInstruction', + identifier: 'myInstruction', subInstructions: [ - instructionNode({ name: 'mySubInstruction1' }), - instructionNode({ name: 'mySubInstruction2' }), + instructionNode({ identifier: 'mySubInstruction1' }), + instructionNode({ identifier: 'mySubInstruction2' }), ], }); @@ -127,8 +114,8 @@ test('sub instructions', () => { test('status mode', () => { const nodeWithStatus = instructionNode({ - name: 'deprecatedInstruction', - status: instructionStatusNode('deprecated', 'Use newInstruction instead'), + identifier: 'deprecatedInstruction', + status: instructionStatusNode('deprecated', { message: 'Use newInstruction instead' }), }); expectMergeVisitorCount(nodeWithStatus, 2); diff --git a/packages/visitors-core/test/nodes/InstructionRemainingAccountsNode.test.ts b/packages/visitors-core/test/nodes/InstructionRemainingAccountsNode.test.ts index 1ef699a57..666b900a5 100644 --- a/packages/visitors-core/test/nodes/InstructionRemainingAccountsNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionRemainingAccountsNode.test.ts @@ -1,4 +1,4 @@ -import { argumentValueNode, instructionRemainingAccountsNode } from '@codama/nodes'; +import { instructionRemainingAccountsNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,13 +8,13 @@ import { expectMergeVisitorCount, } from './_setup'; -const node = instructionRemainingAccountsNode(argumentValueNode('remainingAccounts'), { +const node = instructionRemainingAccountsNode('remainingAccounts', { isSigner: 'either', isWritable: true, }); test('mergeVisitor', () => { - expectMergeVisitorCount(node, 2); + expectMergeVisitorCount(node, 1); }); test('identityVisitor', () => { @@ -23,14 +23,8 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[instructionRemainingAccountsNode]', null); - expectDeleteNodesVisitor(node, '[argumentValueNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -instructionRemainingAccountsNode [writable.optionalSigner] -| argumentValueNode [remainingAccounts]`, - ); + expectDebugStringVisitor(node, `instructionRemainingAccountsNode [writable.optionalSigner]`); }); diff --git a/packages/visitors-core/test/nodes/InstructionStatusNode.test.ts b/packages/visitors-core/test/nodes/InstructionStatusNode.test.ts index 5da8bdce5..75fd350ac 100644 --- a/packages/visitors-core/test/nodes/InstructionStatusNode.test.ts +++ b/packages/visitors-core/test/nodes/InstructionStatusNode.test.ts @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from './_setup'; -const node = instructionStatusNode('live', 'Public release.'); +const node = instructionStatusNode('live', { message: 'Public release.' }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); diff --git a/packages/visitors-core/test/nodes/PdaNode.test.ts b/packages/visitors-core/test/nodes/PdaNode.test.ts index 46469aae0..cdb31f1c2 100644 --- a/packages/visitors-core/test/nodes/PdaNode.test.ts +++ b/packages/visitors-core/test/nodes/PdaNode.test.ts @@ -1,7 +1,7 @@ import { constantPdaSeedNode, - numberTypeNode, - numberValueNode, + integerTypeNode, + integerValueNode, pdaNode, publicKeyTypeNode, variablePdaSeedNode, @@ -16,10 +16,10 @@ import { } from './_setup'; const node = pdaNode({ - name: 'associatedToken', + identifier: 'associatedToken', seeds: [ variablePdaSeedNode('owner', publicKeyTypeNode()), - constantPdaSeedNode(numberTypeNode('u8'), numberValueNode(123456)), + constantPdaSeedNode(integerTypeNode('u8'), integerValueNode('123456')), variablePdaSeedNode('mint', publicKeyTypeNode()), ], }); @@ -37,7 +37,7 @@ test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, ['[variablePdaSeedNode]', '[constantPdaSeedNode]'], { ...node, seeds: undefined }); expectDeleteNodesVisitor(node, '[publicKeyTypeNode]', { ...node, - seeds: [constantPdaSeedNode(numberTypeNode('u8'), numberValueNode(123456))], + seeds: [constantPdaSeedNode(integerTypeNode('u8'), integerValueNode('123456'))], }); }); @@ -49,8 +49,8 @@ pdaNode [associatedToken] | variablePdaSeedNode [owner] | | publicKeyTypeNode | constantPdaSeedNode -| | numberTypeNode [u8] -| | numberValueNode [123456] +| | integerTypeNode [u8] +| | integerValueNode [123456] | variablePdaSeedNode [mint] | | publicKeyTypeNode`, ); diff --git a/packages/visitors-core/test/nodes/ProgramNode.test.ts b/packages/visitors-core/test/nodes/ProgramNode.test.ts index 1c7d6a6e9..eb210af9f 100644 --- a/packages/visitors-core/test/nodes/ProgramNode.test.ts +++ b/packages/visitors-core/test/nodes/ProgramNode.test.ts @@ -20,18 +20,18 @@ import { const node = programNode({ accounts: [ - accountNode({ data: structTypeNode([]), name: 'mint' }), - accountNode({ data: structTypeNode([]), name: 'token' }), + accountNode({ data: structTypeNode([]), identifier: 'mint' }), + accountNode({ data: structTypeNode([]), identifier: 'token' }), ], - definedTypes: [definedTypeNode({ name: 'tokenState', type: enumTypeNode([]) })], + definedTypes: [definedTypeNode({ identifier: 'tokenState', type: enumTypeNode([]) })], errors: [ - errorNode({ code: 1, message: 'Invalid mint', name: 'invalidMint' }), - errorNode({ code: 2, message: 'Invalid token', name: 'invalidToken' }), + errorNode({ code: 1, identifier: 'invalidMint', message: 'Invalid mint' }), + errorNode({ code: 2, identifier: 'invalidToken', message: 'Invalid token' }), ], - events: [eventNode({ data: structTypeNode([]), name: 'transferEvent' })], - instructions: [instructionNode({ name: 'mintTokens' }), instructionNode({ name: 'transferTokens' })], - name: 'splToken', - pdas: [pdaNode({ name: 'associatedToken', seeds: [] })], + events: [eventNode({ data: structTypeNode([]), identifier: 'transferEvent' })], + identifier: 'splToken', + instructions: [instructionNode({ identifier: 'mintTokens' }), instructionNode({ identifier: 'transferTokens' })], + pdas: [pdaNode({ identifier: 'associatedToken', seeds: [] })], publicKey: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', version: '1.2.3', }); @@ -70,7 +70,7 @@ programNode [splToken.TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA] | instructionNode [transferTokens] | definedTypeNode [tokenState] | | enumTypeNode -| | | numberTypeNode [u8] +| | | integerTypeNode [u8] | errorNode [1.invalidMint] | errorNode [2.invalidToken]`, ); diff --git a/packages/visitors-core/test/nodes/ProvidedNode.test.ts b/packages/visitors-core/test/nodes/ProvidedNode.test.ts index 51e331c7a..8bc154e95 100644 --- a/packages/visitors-core/test/nodes/ProvidedNode.test.ts +++ b/packages/visitors-core/test/nodes/ProvidedNode.test.ts @@ -1,4 +1,4 @@ -import { argumentValueNode, providedNode } from '@codama/nodes'; +import { dataValueNode, providedNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from './_setup'; -const node = providedNode('decimals', argumentValueNode('decimals')); +const node = providedNode('decimals', dataValueNode('decimals')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -20,7 +20,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[providedNode]', null); - expectDeleteNodesVisitor(node, '[argumentValueNode]', null); + expectDeleteNodesVisitor(node, '[dataValueNode]', null); }); test('debugStringVisitor', () => { @@ -28,6 +28,6 @@ test('debugStringVisitor', () => { node, ` providedNode [decimals] -| argumentValueNode [decimals]`, +| dataValueNode [decimals]`, ); }); diff --git a/packages/visitors-core/test/nodes/RootNode.test.ts b/packages/visitors-core/test/nodes/RootNode.test.ts index ceae1cf47..b9519f97d 100644 --- a/packages/visitors-core/test/nodes/RootNode.test.ts +++ b/packages/visitors-core/test/nodes/RootNode.test.ts @@ -10,15 +10,17 @@ import { const node = rootNode( programNode({ - name: 'splToken', + identifier: 'splToken', publicKey: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', }), - [ - programNode({ - name: 'splAddressLookupTable', - publicKey: 'AddressLookupTab1e1111111111111111111111111', - }), - ], + { + additionalPrograms: [ + programNode({ + identifier: 'splAddressLookupTable', + publicKey: 'AddressLookupTab1e1111111111111111111111111', + }), + ], + }, ); test('mergeVisitor', () => { diff --git a/packages/visitors-core/test/nodes/TextNode.test.ts b/packages/visitors-core/test/nodes/TextNode.test.ts new file mode 100644 index 000000000..104c8cf47 --- /dev/null +++ b/packages/visitors-core/test/nodes/TextNode.test.ts @@ -0,0 +1,27 @@ +import { textNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from './_setup'; + +const node = textNode({ content: 'hello' }); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 1); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[textNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor(node, `textNode [hello]`); +}); diff --git a/packages/visitors-core/test/nodes/displayNodes/DateTimeNumberDisplayNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/AccountDataValueNode.test.ts similarity index 59% rename from packages/visitors-core/test/nodes/displayNodes/DateTimeNumberDisplayNode.test.ts rename to packages/visitors-core/test/nodes/contextualValueNodes/AccountDataValueNode.test.ts index b070d9302..fe9e80b25 100644 --- a/packages/visitors-core/test/nodes/displayNodes/DateTimeNumberDisplayNode.test.ts +++ b/packages/visitors-core/test/nodes/contextualValueNodes/AccountDataValueNode.test.ts @@ -1,4 +1,4 @@ -import { dateTimeNumberDisplayNode } from '@codama/nodes'; +import { accountDataValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = dateTimeNumberDisplayNode({ ticksPerSecond: 1000 }); +const node = accountDataValueNode('authority', { path: 'balance' }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); @@ -19,9 +19,9 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[dateTimeNumberDisplayNode]', null); + expectDeleteNodesVisitor(node, '[accountDataValueNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `dateTimeNumberDisplayNode`); + expectDebugStringVisitor(node, `accountDataValueNode [authority.balance]`); }); diff --git a/packages/visitors-core/test/nodes/contextualValueNodes/AccountFieldValueNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/AccountFieldValueNode.test.ts deleted file mode 100644 index a2d477974..000000000 --- a/packages/visitors-core/test/nodes/contextualValueNodes/AccountFieldValueNode.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { accountFieldValueNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = accountFieldValueNode({ account: 'mint', path: 'decimals' }); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 1); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[accountFieldValueNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `accountFieldValueNode`); -}); diff --git a/packages/visitors-core/test/nodes/contextualValueNodes/ConditionalValueNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/ConditionalValueNode.test.ts index e8774c843..5329a997d 100644 --- a/packages/visitors-core/test/nodes/contextualValueNodes/ConditionalValueNode.test.ts +++ b/packages/visitors-core/test/nodes/contextualValueNodes/ConditionalValueNode.test.ts @@ -1,6 +1,6 @@ import { accountValueNode, - argumentValueNode, + dataValueNode, conditionalValueNode, enumValueNode, programIdValueNode, @@ -15,7 +15,7 @@ import { } from '../_setup'; const node = conditionalValueNode({ - condition: argumentValueNode('tokenStandard'), + condition: dataValueNode('tokenStandard'), ifFalse: programIdValueNode(), ifTrue: accountValueNode('mint'), value: enumValueNode('tokenStandard', 'ProgrammableNonFungible'), @@ -42,8 +42,8 @@ test('debugStringVisitor', () => { node, ` conditionalValueNode -| argumentValueNode [tokenStandard] -| enumValueNode [programmableNonFungible] +| dataValueNode [tokenStandard] +| enumValueNode [ProgrammableNonFungible] | | definedTypeLinkNode [tokenStandard] | accountValueNode [mint] | programIdValueNode`, diff --git a/packages/visitors-core/test/nodes/contextualValueNodes/ArgumentValueNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/DataValueNode.test.ts similarity index 64% rename from packages/visitors-core/test/nodes/contextualValueNodes/ArgumentValueNode.test.ts rename to packages/visitors-core/test/nodes/contextualValueNodes/DataValueNode.test.ts index 4b07dd429..092b7e535 100644 --- a/packages/visitors-core/test/nodes/contextualValueNodes/ArgumentValueNode.test.ts +++ b/packages/visitors-core/test/nodes/contextualValueNodes/DataValueNode.test.ts @@ -1,4 +1,4 @@ -import { argumentValueNode } from '@codama/nodes'; +import { dataValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = argumentValueNode('space'); +const node = dataValueNode('config.bump'); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); @@ -19,9 +19,9 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[argumentValueNode]', null); + expectDeleteNodesVisitor(node, '[dataValueNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `argumentValueNode [space]`); + expectDebugStringVisitor(node, `dataValueNode [config.bump]`); }); diff --git a/packages/visitors-core/test/nodes/contextualValueNodes/PdaValueNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/PdaValueNode.test.ts index e42d3dc2a..d5ca5bf1b 100644 --- a/packages/visitors-core/test/nodes/contextualValueNodes/PdaValueNode.test.ts +++ b/packages/visitors-core/test/nodes/contextualValueNodes/PdaValueNode.test.ts @@ -18,10 +18,12 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = pdaValueNode(pdaLinkNode('associatedToken'), [ - pdaSeedValueNode('mint', accountValueNode('mint')), - pdaSeedValueNode('owner', publicKeyValueNode('8sphVBHQxufE4Jc1HMuWwWdKgoDjncQyPHwxYhfATRtF')), -]); +const node = pdaValueNode(pdaLinkNode('associatedToken'), { + seeds: [ + pdaSeedValueNode('mint', accountValueNode('mint')), + pdaSeedValueNode('owner', publicKeyValueNode('8sphVBHQxufE4Jc1HMuWwWdKgoDjncQyPHwxYhfATRtF')), + ], +}); test('mergeVisitor', () => { expectMergeVisitorCount(node, 6); @@ -33,7 +35,7 @@ test('identityVisitor', () => { test('identityVisitor with inlined PdaNode', () => { const inlinedPdaNode = pdaNode({ - name: 'associatedToken', + identifier: 'associatedToken', seeds: [ variablePdaSeedNode('mint', publicKeyTypeNode()), constantPdaSeedNode( @@ -43,10 +45,12 @@ test('identityVisitor with inlined PdaNode', () => { variablePdaSeedNode('owner', publicKeyTypeNode()), ], }); - const inlinedPdaValueNode = pdaValueNode(inlinedPdaNode, [ - pdaSeedValueNode('mint', accountValueNode('mint')), - pdaSeedValueNode('owner', publicKeyValueNode('8sphVBHQxufE4Jc1HMuWwWdKgoDjncQyPHwxYhfATRtF')), - ]); + const inlinedPdaValueNode = pdaValueNode(inlinedPdaNode, { + seeds: [ + pdaSeedValueNode('mint', accountValueNode('mint')), + pdaSeedValueNode('owner', publicKeyValueNode('8sphVBHQxufE4Jc1HMuWwWdKgoDjncQyPHwxYhfATRtF')), + ], + }); expectIdentityVisitor(inlinedPdaValueNode); }); diff --git a/packages/visitors-core/test/nodes/contextualValueNodes/ResolverValueNode.test.ts b/packages/visitors-core/test/nodes/contextualValueNodes/ResolverValueNode.test.ts deleted file mode 100644 index 0de2f4b16..000000000 --- a/packages/visitors-core/test/nodes/contextualValueNodes/ResolverValueNode.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { accountValueNode, argumentValueNode, resolverValueNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = resolverValueNode('myCustomResolver', { - dependsOn: [accountValueNode('mint'), argumentValueNode('tokenStandard')], -}); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 3); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[resolverValueNode]', null); - expectDeleteNodesVisitor(node, ['[accountValueNode]', '[argumentValueNode]'], { ...node, dependsOn: undefined }); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -resolverValueNode [myCustomResolver] -| accountValueNode [mint] -| argumentValueNode [tokenStandard]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/countNodes/PrefixedCountNode.test.ts b/packages/visitors-core/test/nodes/countNodes/PrefixedCountNode.test.ts index 7c73c831e..8185ebd6d 100644 --- a/packages/visitors-core/test/nodes/countNodes/PrefixedCountNode.test.ts +++ b/packages/visitors-core/test/nodes/countNodes/PrefixedCountNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, prefixedCountNode } from '@codama/nodes'; +import { integerTypeNode, prefixedCountNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = prefixedCountNode(numberTypeNode('u32')); +const node = prefixedCountNode(integerTypeNode('u32')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -20,7 +20,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[prefixedCountNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { @@ -28,6 +28,6 @@ test('debugStringVisitor', () => { node, ` prefixedCountNode -| numberTypeNode [u32]`, +| integerTypeNode [u32]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/SentinelTypeNode.test.ts b/packages/visitors-core/test/nodes/countNodes/SentinelCountNode.test.ts similarity index 52% rename from packages/visitors-core/test/nodes/typeNodes/SentinelTypeNode.test.ts rename to packages/visitors-core/test/nodes/countNodes/SentinelCountNode.test.ts index b72e9db76..b6817584d 100644 --- a/packages/visitors-core/test/nodes/typeNodes/SentinelTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/countNodes/SentinelCountNode.test.ts @@ -1,4 +1,4 @@ -import { constantValueNodeFromBytes, sentinelTypeNode, stringTypeNode } from '@codama/nodes'; +import { constantValueNode, integerTypeNode, integerValueNode, sentinelCountNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,10 +8,10 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = sentinelTypeNode(stringTypeNode('utf8'), constantValueNodeFromBytes('base16', 'ffff')); +const node = sentinelCountNode(constantValueNode(integerTypeNode('u8'), integerValueNode('0'))); test('mergeVisitor', () => { - expectMergeVisitorCount(node, 5); + expectMergeVisitorCount(node, 4); }); test('identityVisitor', () => { @@ -19,8 +19,7 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[sentinelTypeNode]', null); - expectDeleteNodesVisitor(node, '[stringTypeNode]', null); + expectDeleteNodesVisitor(node, '[sentinelCountNode]', null); expectDeleteNodesVisitor(node, '[constantValueNode]', null); }); @@ -28,10 +27,9 @@ test('debugStringVisitor', () => { expectDebugStringVisitor( node, ` -sentinelTypeNode +sentinelCountNode | constantValueNode -| | bytesTypeNode -| | bytesValueNode [base16.ffff] -| stringTypeNode [utf8]`, +| | integerTypeNode [u8] +| | integerValueNode [0]`, ); }); diff --git a/packages/visitors-core/test/nodes/discriminatorNodes/ConstantDiscriminatorNode.test.ts b/packages/visitors-core/test/nodes/discriminatorNodes/ConstantDiscriminatorNode.test.ts index f4c565390..d34dd657f 100644 --- a/packages/visitors-core/test/nodes/discriminatorNodes/ConstantDiscriminatorNode.test.ts +++ b/packages/visitors-core/test/nodes/discriminatorNodes/ConstantDiscriminatorNode.test.ts @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = constantDiscriminatorNode(constantValueNodeFromBytes('base16', '01020304'), 42); +const node = constantDiscriminatorNode(constantValueNodeFromBytes('base16', '01020304'), { offset: 42 }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 4); diff --git a/packages/visitors-core/test/nodes/discriminatorNodes/FieldDiscriminatorNode.test.ts b/packages/visitors-core/test/nodes/discriminatorNodes/FieldDiscriminatorNode.test.ts index 899e81aff..27d231a44 100644 --- a/packages/visitors-core/test/nodes/discriminatorNodes/FieldDiscriminatorNode.test.ts +++ b/packages/visitors-core/test/nodes/discriminatorNodes/FieldDiscriminatorNode.test.ts @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = fieldDiscriminatorNode('discriminator', 42); +const node = fieldDiscriminatorNode('discriminator', { offset: 42 }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); diff --git a/packages/visitors-core/test/nodes/displayNodes/AmountNumberDisplayNode.test.ts b/packages/visitors-core/test/nodes/displayNodes/AmountNumberDisplayNode.test.ts index ee97512cd..5c043bee2 100644 --- a/packages/visitors-core/test/nodes/displayNodes/AmountNumberDisplayNode.test.ts +++ b/packages/visitors-core/test/nodes/displayNodes/AmountNumberDisplayNode.test.ts @@ -1,4 +1,4 @@ -import { amountNumberDisplayNode, injectedValueNode, numberValueNode } from '@codama/nodes'; +import { amountNumberDisplayNode, injectedValueNode, integerValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -9,7 +9,7 @@ import { } from '../_setup'; const node = amountNumberDisplayNode({ - decimals: numberValueNode(6), + decimals: integerValueNode('6'), unit: injectedValueNode({ key: 'symbol' }), }); @@ -23,12 +23,9 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[amountNumberDisplayNode]', null); - expectDeleteNodesVisitor( - node, - '[numberValueNode]', - amountNumberDisplayNode({ unit: injectedValueNode({ key: 'symbol' }) }), - ); - expectDeleteNodesVisitor(node, '[injectedValueNode]', amountNumberDisplayNode({ decimals: numberValueNode(6) })); + // `decimals` is required, so removing it removes the whole node. + expectDeleteNodesVisitor(node, '[integerValueNode]', null); + expectDeleteNodesVisitor(node, '[injectedValueNode]', amountNumberDisplayNode({ decimals: integerValueNode('6') })); }); test('debugStringVisitor', () => { @@ -36,7 +33,7 @@ test('debugStringVisitor', () => { node, ` amountNumberDisplayNode -| numberValueNode [6] -| injectedValueNode`, +| integerValueNode [6] +| injectedValueNode [symbol]`, ); }); diff --git a/packages/visitors-core/test/nodes/displayNodes/DurationNumberDisplayNode.test.ts b/packages/visitors-core/test/nodes/displayNodes/DurationNumberDisplayNode.test.ts deleted file mode 100644 index 160f2d599..000000000 --- a/packages/visitors-core/test/nodes/displayNodes/DurationNumberDisplayNode.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { durationNumberDisplayNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = durationNumberDisplayNode({ ticksPerSecond: 1 }); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 1); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[durationNumberDisplayNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `durationNumberDisplayNode`); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/SolAmountTypeNode.test.ts b/packages/visitors-core/test/nodes/displayNodes/UnitNumberDisplayNode.test.ts similarity index 58% rename from packages/visitors-core/test/nodes/typeNodes/SolAmountTypeNode.test.ts rename to packages/visitors-core/test/nodes/displayNodes/UnitNumberDisplayNode.test.ts index ddd13f772..29100f0ce 100644 --- a/packages/visitors-core/test/nodes/typeNodes/SolAmountTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/displayNodes/UnitNumberDisplayNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, solAmountTypeNode } from '@codama/nodes'; +import { stringValueNode, unitNumberDisplayNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = solAmountTypeNode(numberTypeNode('u64')); +const node = unitNumberDisplayNode({ unit: stringValueNode('SOL') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -19,15 +19,15 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[solAmountTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[unitNumberDisplayNode]', null); + expectDeleteNodesVisitor(node, '[stringValueNode]', null); }); test('debugStringVisitor', () => { expectDebugStringVisitor( node, ` -solAmountTypeNode -| numberTypeNode [u64]`, +unitNumberDisplayNode +| stringValueNode [SOL]`, ); }); diff --git a/packages/visitors-core/test/nodes/linkNodes/AccountLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/AccountLinkNode.test.ts index 7519180db..be74b0f0f 100644 --- a/packages/visitors-core/test/nodes/linkNodes/AccountLinkNode.test.ts +++ b/packages/visitors-core/test/nodes/linkNodes/AccountLinkNode.test.ts @@ -1,4 +1,4 @@ -import { accountLinkNode } from '@codama/nodes'; +import { accountLinkNode, programLinkNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = accountLinkNode('token', 'splToken'); +const node = accountLinkNode('token', { program: programLinkNode('splToken') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); diff --git a/packages/visitors-core/test/nodes/linkNodes/DefinedTypeLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/DefinedTypeLinkNode.test.ts index feb2d3ffe..3bf060b95 100644 --- a/packages/visitors-core/test/nodes/linkNodes/DefinedTypeLinkNode.test.ts +++ b/packages/visitors-core/test/nodes/linkNodes/DefinedTypeLinkNode.test.ts @@ -1,4 +1,4 @@ -import { definedTypeLinkNode } from '@codama/nodes'; +import { definedTypeLinkNode, programLinkNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = definedTypeLinkNode('tokenState', 'splToken'); +const node = definedTypeLinkNode('tokenState', { program: programLinkNode('splToken') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); diff --git a/packages/visitors-core/test/nodes/linkNodes/InstructionAccountLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/InstructionAccountLinkNode.test.ts index 69921396f..4fe815123 100644 --- a/packages/visitors-core/test/nodes/linkNodes/InstructionAccountLinkNode.test.ts +++ b/packages/visitors-core/test/nodes/linkNodes/InstructionAccountLinkNode.test.ts @@ -1,4 +1,4 @@ -import { instructionAccountLinkNode, instructionLinkNode } from '@codama/nodes'; +import { instructionAccountLinkNode, instructionLinkNode, programLinkNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,9 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = instructionAccountLinkNode('mint', instructionLinkNode('transferTokens', 'splToken')); +const node = instructionAccountLinkNode('mint', { + instruction: instructionLinkNode('transferTokens', { program: programLinkNode('splToken') }), +}); test('mergeVisitor', () => { expectMergeVisitorCount(node, 3); @@ -21,7 +23,11 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[instructionAccountLinkNode]', null); expectDeleteNodesVisitor(node, '[instructionLinkNode]', instructionAccountLinkNode('mint')); - expectDeleteNodesVisitor(node, '[programLinkNode]', instructionAccountLinkNode('mint', 'transferTokens')); + expectDeleteNodesVisitor( + node, + '[programLinkNode]', + instructionAccountLinkNode('mint', { instruction: instructionLinkNode('transferTokens') }), + ); }); test('debugStringVisitor', () => { diff --git a/packages/visitors-core/test/nodes/linkNodes/InstructionArgumentLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/InstructionArgumentLinkNode.test.ts deleted file mode 100644 index 1cdc14fac..000000000 --- a/packages/visitors-core/test/nodes/linkNodes/InstructionArgumentLinkNode.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { instructionArgumentLinkNode, instructionLinkNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = instructionArgumentLinkNode('amount', instructionLinkNode('transferTokens', 'splToken')); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 3); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[instructionArgumentLinkNode]', null); - expectDeleteNodesVisitor(node, '[instructionLinkNode]', instructionArgumentLinkNode('amount')); - expectDeleteNodesVisitor(node, '[programLinkNode]', instructionArgumentLinkNode('amount', 'transferTokens')); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -instructionArgumentLinkNode [amount] -| instructionLinkNode [transferTokens] -| | programLinkNode [splToken]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/linkNodes/InstructionLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/InstructionLinkNode.test.ts index dc61d8345..fba52a6cd 100644 --- a/packages/visitors-core/test/nodes/linkNodes/InstructionLinkNode.test.ts +++ b/packages/visitors-core/test/nodes/linkNodes/InstructionLinkNode.test.ts @@ -1,4 +1,4 @@ -import { instructionLinkNode } from '@codama/nodes'; +import { instructionLinkNode, programLinkNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = instructionLinkNode('transferTokens', 'splToken'); +const node = instructionLinkNode('transferTokens', { program: programLinkNode('splToken') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); diff --git a/packages/visitors-core/test/nodes/linkNodes/PdaLinkNode.test.ts b/packages/visitors-core/test/nodes/linkNodes/PdaLinkNode.test.ts index b0a7f8a83..197e66651 100644 --- a/packages/visitors-core/test/nodes/linkNodes/PdaLinkNode.test.ts +++ b/packages/visitors-core/test/nodes/linkNodes/PdaLinkNode.test.ts @@ -1,4 +1,4 @@ -import { pdaLinkNode } from '@codama/nodes'; +import { pdaLinkNode, programLinkNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = pdaLinkNode('associatedToken', 'splToken'); +const node = pdaLinkNode('associatedToken', { program: programLinkNode('splToken') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); diff --git a/packages/visitors-core/test/nodes/pdaSeedNodes/ConstantPdaSeedNode.test.ts b/packages/visitors-core/test/nodes/pdaSeedNodes/ConstantPdaSeedNode.test.ts index d0ce42ffc..45a719829 100644 --- a/packages/visitors-core/test/nodes/pdaSeedNodes/ConstantPdaSeedNode.test.ts +++ b/packages/visitors-core/test/nodes/pdaSeedNodes/ConstantPdaSeedNode.test.ts @@ -1,4 +1,4 @@ -import { constantPdaSeedNode, numberTypeNode, numberValueNode } from '@codama/nodes'; +import { constantPdaSeedNode, integerTypeNode, integerValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = constantPdaSeedNode(numberTypeNode('u8'), numberValueNode(42)); +const node = constantPdaSeedNode(integerTypeNode('u8'), integerValueNode('42')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 3); @@ -20,8 +20,8 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[constantPdaSeedNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberValueNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerValueNode]', null); }); test('debugStringVisitor', () => { @@ -29,7 +29,7 @@ test('debugStringVisitor', () => { node, ` constantPdaSeedNode -| numberTypeNode [u8] -| numberValueNode [42]`, +| integerTypeNode [u8] +| integerValueNode [42]`, ); }); diff --git a/packages/visitors-core/test/nodes/transformNodes/FixedSizeTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/FixedSizeTransformNode.test.ts new file mode 100644 index 000000000..b8011e4dd --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/FixedSizeTransformNode.test.ts @@ -0,0 +1,27 @@ +import { fixedSizeTransformNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = fixedSizeTransformNode(32); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 1); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[fixedSizeTransformNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor(node, `fixedSizeTransformNode [32]`); +}); diff --git a/packages/visitors-core/test/nodes/transformNodes/HiddenPrefixTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/HiddenPrefixTransformNode.test.ts new file mode 100644 index 000000000..cb4f1f76e --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/HiddenPrefixTransformNode.test.ts @@ -0,0 +1,35 @@ +import { constantValueNode, hiddenPrefixTransformNode, integerTypeNode, integerValueNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = hiddenPrefixTransformNode([constantValueNode(integerTypeNode('u8'), integerValueNode('0'))]); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 4); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[hiddenPrefixTransformNode]', null); + expectDeleteNodesVisitor(node, '[constantValueNode]', hiddenPrefixTransformNode([])); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor( + node, + ` +hiddenPrefixTransformNode +| constantValueNode +| | integerTypeNode [u8] +| | integerValueNode [0]`, + ); +}); diff --git a/packages/visitors-core/test/nodes/transformNodes/HiddenSuffixTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/HiddenSuffixTransformNode.test.ts new file mode 100644 index 000000000..4d4ac1329 --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/HiddenSuffixTransformNode.test.ts @@ -0,0 +1,35 @@ +import { constantValueNode, hiddenSuffixTransformNode, integerTypeNode, integerValueNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = hiddenSuffixTransformNode([constantValueNode(integerTypeNode('u8'), integerValueNode('0'))]); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 4); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[hiddenSuffixTransformNode]', null); + expectDeleteNodesVisitor(node, '[constantValueNode]', hiddenSuffixTransformNode([])); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor( + node, + ` +hiddenSuffixTransformNode +| constantValueNode +| | integerTypeNode [u8] +| | integerValueNode [0]`, + ); +}); diff --git a/packages/visitors-core/test/nodes/transformNodes/PostOffsetTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/PostOffsetTransformNode.test.ts new file mode 100644 index 000000000..fe898f3ec --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/PostOffsetTransformNode.test.ts @@ -0,0 +1,27 @@ +import { postOffsetTransformNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = postOffsetTransformNode(4); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 1); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[postOffsetTransformNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor(node, `postOffsetTransformNode [4.relative]`); +}); diff --git a/packages/visitors-core/test/nodes/transformNodes/PreOffsetTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/PreOffsetTransformNode.test.ts new file mode 100644 index 000000000..67647468b --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/PreOffsetTransformNode.test.ts @@ -0,0 +1,27 @@ +import { preOffsetTransformNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = preOffsetTransformNode(4); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 1); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[preOffsetTransformNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor(node, `preOffsetTransformNode [4.relative]`); +}); diff --git a/packages/visitors-core/test/nodes/transformNodes/SentinelTransformNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/SentinelTransformNode.test.ts new file mode 100644 index 000000000..30b81cfa7 --- /dev/null +++ b/packages/visitors-core/test/nodes/transformNodes/SentinelTransformNode.test.ts @@ -0,0 +1,35 @@ +import { constantValueNode, integerTypeNode, integerValueNode, sentinelTransformNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = sentinelTransformNode(constantValueNode(integerTypeNode('u8'), integerValueNode('0'))); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 4); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[sentinelTransformNode]', null); + expectDeleteNodesVisitor(node, '[constantValueNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor( + node, + ` +sentinelTransformNode +| constantValueNode +| | integerTypeNode [u8] +| | integerValueNode [0]`, + ); +}); diff --git a/packages/visitors-core/test/nodes/typeNodes/AmountTypeNode.test.ts b/packages/visitors-core/test/nodes/transformNodes/SizePrefixTransformNode.test.ts similarity index 58% rename from packages/visitors-core/test/nodes/typeNodes/AmountTypeNode.test.ts rename to packages/visitors-core/test/nodes/transformNodes/SizePrefixTransformNode.test.ts index d3bc5a398..e3338d5ee 100644 --- a/packages/visitors-core/test/nodes/typeNodes/AmountTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/transformNodes/SizePrefixTransformNode.test.ts @@ -1,4 +1,4 @@ -import { amountTypeNode, numberTypeNode } from '@codama/nodes'; +import { integerTypeNode, sizePrefixTransformNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = amountTypeNode(numberTypeNode('u64'), 9, 'SOL'); +const node = sizePrefixTransformNode(integerTypeNode('u32')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -19,15 +19,15 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[amountTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[sizePrefixTransformNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { expectDebugStringVisitor( node, ` -amountTypeNode [9.SOL] -| numberTypeNode [u64]`, +sizePrefixTransformNode +| integerTypeNode [u32]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/ArrayTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/ArrayTypeNode.test.ts index 877974e35..a10cb8132 100644 --- a/packages/visitors-core/test/nodes/typeNodes/ArrayTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/ArrayTypeNode.test.ts @@ -1,4 +1,4 @@ -import { arrayTypeNode, numberTypeNode, prefixedCountNode, publicKeyTypeNode } from '@codama/nodes'; +import { arrayTypeNode, integerTypeNode, prefixedCountNode, publicKeyTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = arrayTypeNode(publicKeyTypeNode(), prefixedCountNode(numberTypeNode('u64'))); +const node = arrayTypeNode(publicKeyTypeNode(), prefixedCountNode(integerTypeNode('u64'))); test('mergeVisitor', () => { expectMergeVisitorCount(node, 4); @@ -22,7 +22,7 @@ test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[arrayTypeNode]', null); expectDeleteNodesVisitor(node, '[publicKeyTypeNode]', null); expectDeleteNodesVisitor(node, '[prefixedCountNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { @@ -31,7 +31,7 @@ test('debugStringVisitor', () => { ` arrayTypeNode | prefixedCountNode -| | numberTypeNode [u64] +| | integerTypeNode [u64] | publicKeyTypeNode`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/BooleanTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/BooleanTypeNode.test.ts index 6d55ba0c1..7c060c4ba 100644 --- a/packages/visitors-core/test/nodes/typeNodes/BooleanTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/BooleanTypeNode.test.ts @@ -1,4 +1,4 @@ -import { booleanTypeNode, numberTypeNode } from '@codama/nodes'; +import { booleanTypeNode, integerTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = booleanTypeNode(numberTypeNode('u32')); +const node = booleanTypeNode({ size: integerTypeNode('u32') }); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -20,7 +20,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[booleanTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { @@ -28,6 +28,6 @@ test('debugStringVisitor', () => { node, ` booleanTypeNode -| numberTypeNode [u32]`, +| integerTypeNode [u32]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/DateTimeTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/DateTimeTypeNode.test.ts index 01bf51ff5..309569436 100644 --- a/packages/visitors-core/test/nodes/typeNodes/DateTimeTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/DateTimeTypeNode.test.ts @@ -1,4 +1,4 @@ -import { dateTimeTypeNode, numberTypeNode } from '@codama/nodes'; +import { dateTimeTypeNode, integerTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = dateTimeTypeNode(numberTypeNode('u64')); +const node = dateTimeTypeNode(integerTypeNode('u64')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -20,7 +20,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[dateTimeTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { @@ -28,6 +28,6 @@ test('debugStringVisitor', () => { node, ` dateTimeTypeNode -| numberTypeNode [u64]`, +| integerTypeNode [u64]`, ); }); diff --git a/packages/visitors-core/test/nodes/sizeNodes/PrefixedSizeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/DurationTypeNode.test.ts similarity index 61% rename from packages/visitors-core/test/nodes/sizeNodes/PrefixedSizeNode.test.ts rename to packages/visitors-core/test/nodes/typeNodes/DurationTypeNode.test.ts index 0f6063b0d..b058d27ca 100644 --- a/packages/visitors-core/test/nodes/sizeNodes/PrefixedSizeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/DurationTypeNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, prefixedCountNode } from '@codama/nodes'; +import { durationTypeNode, integerTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = prefixedCountNode(numberTypeNode('u64')); +const node = durationTypeNode(integerTypeNode('i64')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -19,15 +19,15 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[prefixedCountNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[durationTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { expectDebugStringVisitor( node, ` -prefixedCountNode -| numberTypeNode [u64]`, +durationTypeNode +| integerTypeNode [i64]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/EnumEmptyVariantTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/EnumEmptyVariantTypeNode.test.ts deleted file mode 100644 index c8f8bd0ee..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/EnumEmptyVariantTypeNode.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { enumEmptyVariantTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = enumEmptyVariantTypeNode('initialized'); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 1); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[enumEmptyVariantTypeNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `enumEmptyVariantTypeNode [initialized]`); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/EnumStructVariantTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/EnumStructVariantTypeNode.test.ts deleted file mode 100644 index 22e5b66cf..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/EnumStructVariantTypeNode.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - numberTypeNode, - structFieldTypeNode, - structTypeNode, -} from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = enumStructVariantTypeNode( - 'mouseClick', - structTypeNode([ - structFieldTypeNode({ name: 'x', type: numberTypeNode('u32') }), - structFieldTypeNode({ name: 'y', type: numberTypeNode('u32') }), - ]), -); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 6); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[enumStructVariantTypeNode]', null); - expectDeleteNodesVisitor(node, '[structTypeNode]', enumEmptyVariantTypeNode('mouseClick')); - expectDeleteNodesVisitor(node, '[structFieldTypeNode]', enumEmptyVariantTypeNode('mouseClick')); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -enumStructVariantTypeNode [mouseClick] -| structTypeNode -| | structFieldTypeNode [x] -| | | numberTypeNode [u32] -| | structFieldTypeNode [y] -| | | numberTypeNode [u32]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/EnumTupleVariantTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/EnumTupleVariantTypeNode.test.ts deleted file mode 100644 index c970cc9ec..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/EnumTupleVariantTypeNode.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { enumEmptyVariantTypeNode, enumTupleVariantTypeNode, numberTypeNode, tupleTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = enumTupleVariantTypeNode('coordinates', tupleTypeNode([numberTypeNode('u32'), numberTypeNode('u32')])); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 4); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[enumTupleVariantTypeNode]', null); - expectDeleteNodesVisitor(node, '[tupleTypeNode]', enumEmptyVariantTypeNode('coordinates')); - expectDeleteNodesVisitor(node, '[numberTypeNode]', enumEmptyVariantTypeNode('coordinates')); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -enumTupleVariantTypeNode [coordinates] -| tupleTypeNode -| | numberTypeNode [u32] -| | numberTypeNode [u32]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/EnumTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/EnumTypeNode.test.ts index 448d57207..ec71ba37e 100644 --- a/packages/visitors-core/test/nodes/typeNodes/EnumTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/EnumTypeNode.test.ts @@ -1,10 +1,9 @@ import { - enumEmptyVariantTypeNode, - enumStructVariantTypeNode, - enumTupleVariantTypeNode, + addTypeNodeTransforms, enumTypeNode, - fixedSizeTypeNode, - numberTypeNode, + enumVariantTypeNode, + fixedSizeTransformNode, + integerTypeNode, stringTypeNode, structFieldTypeNode, structTypeNode, @@ -21,17 +20,18 @@ import { const node = enumTypeNode( [ - enumEmptyVariantTypeNode('quit'), - enumTupleVariantTypeNode('write', tupleTypeNode([fixedSizeTypeNode(stringTypeNode('utf8'), 32)])), - enumStructVariantTypeNode( - 'move', - structTypeNode([ - structFieldTypeNode({ name: 'x', type: numberTypeNode('u32') }), - structFieldTypeNode({ name: 'y', type: numberTypeNode('u32') }), + enumVariantTypeNode('quit'), + enumVariantTypeNode('write', { + data: tupleTypeNode([addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)])]), + }), + enumVariantTypeNode('move', { + data: structTypeNode([ + structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u32') }), + structFieldTypeNode({ identifier: 'y', type: integerTypeNode('u32') }), ]), - ), + }), ], - { size: numberTypeNode('u64') }, + { size: integerTypeNode('u64') }, ); test('mergeVisitor', () => { @@ -44,17 +44,13 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[enumTypeNode]', null); - expectDeleteNodesVisitor( - node, - ['[enumEmptyVariantTypeNode]', '[enumTupleVariantTypeNode]', '[enumStructVariantTypeNode]'], - { ...node, variants: undefined }, - ); + expectDeleteNodesVisitor(node, '[enumVariantTypeNode]', { ...node, variants: undefined }); expectDeleteNodesVisitor(node, ['[tupleTypeNode]', '[structFieldTypeNode]'], { ...node, variants: [ - enumEmptyVariantTypeNode('quit'), - enumEmptyVariantTypeNode('write'), - enumEmptyVariantTypeNode('move'), + enumVariantTypeNode('quit'), + enumVariantTypeNode('write'), + enumVariantTypeNode('move', { data: structTypeNode([]) }), ], }); }); @@ -64,17 +60,17 @@ test('debugStringVisitor', () => { node, ` enumTypeNode -| numberTypeNode [u64] -| enumEmptyVariantTypeNode [quit] -| enumTupleVariantTypeNode [write] +| integerTypeNode [u64] +| enumVariantTypeNode [quit] +| enumVariantTypeNode [write] | | tupleTypeNode -| | | fixedSizeTypeNode [32] -| | | | stringTypeNode [utf8] -| enumStructVariantTypeNode [move] +| | | stringTypeNode [utf8] +| | | | fixedSizeTransformNode [32] +| enumVariantTypeNode [move] | | structTypeNode | | | structFieldTypeNode [x] -| | | | numberTypeNode [u32] +| | | | integerTypeNode [u32] | | | structFieldTypeNode [y] -| | | | numberTypeNode [u32]`, +| | | | integerTypeNode [u32]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/EnumVariantTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/EnumVariantTypeNode.test.ts new file mode 100644 index 000000000..bab9b864d --- /dev/null +++ b/packages/visitors-core/test/nodes/typeNodes/EnumVariantTypeNode.test.ts @@ -0,0 +1,37 @@ +import { enumVariantTypeNode, integerTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = enumVariantTypeNode('move', { + data: structTypeNode([structFieldTypeNode({ identifier: 'x', type: integerTypeNode('u32') })]), +}); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 4); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[enumVariantTypeNode]', null); + expectDeleteNodesVisitor(node, '[structTypeNode]', enumVariantTypeNode('move')); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor( + node, + ` +enumVariantTypeNode [move] +| structTypeNode +| | structFieldTypeNode [x] +| | | integerTypeNode [u32]`, + ); +}); diff --git a/packages/visitors-core/test/nodes/typeNodes/FixedSizeTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/FixedPointTypeNode.test.ts similarity index 59% rename from packages/visitors-core/test/nodes/typeNodes/FixedSizeTypeNode.test.ts rename to packages/visitors-core/test/nodes/typeNodes/FixedPointTypeNode.test.ts index 947de8387..2e3879b00 100644 --- a/packages/visitors-core/test/nodes/typeNodes/FixedSizeTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/FixedPointTypeNode.test.ts @@ -1,4 +1,4 @@ -import { fixedSizeTypeNode, stringTypeNode } from '@codama/nodes'; +import { fixedPointTypeNode, integerTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = fixedSizeTypeNode(stringTypeNode('utf8'), 42); +const node = fixedPointTypeNode(integerTypeNode('i64'), 9); test('mergeVisitor', () => { expectMergeVisitorCount(node, 2); @@ -19,15 +19,15 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[fixedSizeTypeNode]', null); - expectDeleteNodesVisitor(node, '[stringTypeNode]', null); + expectDeleteNodesVisitor(node, '[fixedPointTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { expectDebugStringVisitor( node, ` -fixedSizeTypeNode [42] -| stringTypeNode [utf8]`, +fixedPointTypeNode [scale:9] +| integerTypeNode [i64]`, ); }); diff --git a/packages/visitors-core/test/nodes/sizeNodes/FixedSizeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/FloatTypeNode.test.ts similarity index 66% rename from packages/visitors-core/test/nodes/sizeNodes/FixedSizeNode.test.ts rename to packages/visitors-core/test/nodes/typeNodes/FloatTypeNode.test.ts index 1b49e1bf6..6aeeafbe6 100644 --- a/packages/visitors-core/test/nodes/sizeNodes/FixedSizeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/FloatTypeNode.test.ts @@ -1,4 +1,4 @@ -import { fixedCountNode } from '@codama/nodes'; +import { floatTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = fixedCountNode(42); +const node = floatTypeNode('f64'); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); @@ -19,9 +19,9 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[fixedCountNode]', null); + expectDeleteNodesVisitor(node, '[floatTypeNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `fixedCountNode [42]`); + expectDebugStringVisitor(node, `floatTypeNode [f64]`); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/HiddenPrefixTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/HiddenPrefixTypeNode.test.ts deleted file mode 100644 index 14c39469f..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/HiddenPrefixTypeNode.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - constantValueNodeFromBytes, - constantValueNodeFromString, - hiddenPrefixTypeNode, - numberTypeNode, -} from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = hiddenPrefixTypeNode(numberTypeNode('u32'), [ - constantValueNodeFromString('utf8', 'hello world'), - constantValueNodeFromBytes('base16', 'ffff'), -]); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 8); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[hiddenPrefixTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor(node, '[constantValueNode]', numberTypeNode('u32')); - expectDeleteNodesVisitor( - node, - '[stringTypeNode]', - hiddenPrefixTypeNode(numberTypeNode('u32'), [constantValueNodeFromBytes('base16', 'ffff')]), - ); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -hiddenPrefixTypeNode -| constantValueNode -| | stringTypeNode [utf8] -| | stringValueNode [hello world] -| constantValueNode -| | bytesTypeNode -| | bytesValueNode [base16.ffff] -| numberTypeNode [u32]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/HiddenSuffixTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/HiddenSuffixTypeNode.test.ts deleted file mode 100644 index f892aab70..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/HiddenSuffixTypeNode.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - constantValueNodeFromBytes, - constantValueNodeFromString, - hiddenSuffixTypeNode, - numberTypeNode, -} from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = hiddenSuffixTypeNode(numberTypeNode('u32'), [ - constantValueNodeFromString('utf8', 'hello world'), - constantValueNodeFromBytes('base16', 'ffff'), -]); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 8); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[hiddenSuffixTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor(node, '[constantValueNode]', numberTypeNode('u32')); - expectDeleteNodesVisitor( - node, - '[stringTypeNode]', - hiddenSuffixTypeNode(numberTypeNode('u32'), [constantValueNodeFromBytes('base16', 'ffff')]), - ); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -hiddenSuffixTypeNode -| numberTypeNode [u32] -| constantValueNode -| | stringTypeNode [utf8] -| | stringValueNode [hello world] -| constantValueNode -| | bytesTypeNode -| | bytesValueNode [base16.ffff]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/sizeNodes/RemainderSizeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/IntegerTypeNode.test.ts similarity index 65% rename from packages/visitors-core/test/nodes/sizeNodes/RemainderSizeNode.test.ts rename to packages/visitors-core/test/nodes/typeNodes/IntegerTypeNode.test.ts index c320b1379..f5846b6c4 100644 --- a/packages/visitors-core/test/nodes/sizeNodes/RemainderSizeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/IntegerTypeNode.test.ts @@ -1,4 +1,4 @@ -import { remainderCountNode } from '@codama/nodes'; +import { integerTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = remainderCountNode(); +const node = integerTypeNode('u64'); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); @@ -19,9 +19,9 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[remainderCountNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `remainderCountNode`); + expectDebugStringVisitor(node, `integerTypeNode [u64]`); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/MapTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/MapTypeNode.test.ts index 6f2ad5cbd..dca151e06 100644 --- a/packages/visitors-core/test/nodes/typeNodes/MapTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/MapTypeNode.test.ts @@ -1,7 +1,8 @@ import { - fixedSizeTypeNode, + addTypeNodeTransforms, + fixedSizeTransformNode, + integerTypeNode, mapTypeNode, - numberTypeNode, prefixedCountNode, publicKeyTypeNode, stringTypeNode, @@ -16,9 +17,9 @@ import { } from '../_setup'; const node = mapTypeNode( - fixedSizeTypeNode(stringTypeNode('utf8'), 32), + addTypeNodeTransforms(stringTypeNode('utf8'), [fixedSizeTransformNode(32)]), publicKeyTypeNode(), - prefixedCountNode(numberTypeNode('u8')), + prefixedCountNode(integerTypeNode('u8')), ); test('mergeVisitor', () => { @@ -42,9 +43,9 @@ test('debugStringVisitor', () => { ` mapTypeNode | prefixedCountNode -| | numberTypeNode [u8] -| fixedSizeTypeNode [32] -| | stringTypeNode [utf8] +| | integerTypeNode [u8] +| stringTypeNode [utf8] +| | fixedSizeTransformNode [32] | publicKeyTypeNode`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/NumberTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/NumberTypeNode.test.ts deleted file mode 100644 index 907741d53..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/NumberTypeNode.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { numberTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = numberTypeNode('f64'); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 1); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `numberTypeNode [f64]`); - expectDebugStringVisitor(numberTypeNode('f64', 'be'), `numberTypeNode [f64.bigEndian]`); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/OptionTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/OptionTypeNode.test.ts index ac0dac0dd..7feb8d829 100644 --- a/packages/visitors-core/test/nodes/typeNodes/OptionTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/OptionTypeNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, optionTypeNode, publicKeyTypeNode } from '@codama/nodes'; +import { integerTypeNode, optionTypeNode, publicKeyTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -10,7 +10,7 @@ import { const node = optionTypeNode(publicKeyTypeNode(), { fixed: true, - prefix: numberTypeNode('u64'), + prefix: integerTypeNode('u64'), }); test('mergeVisitor', () => { @@ -24,7 +24,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[optionTypeNode]', null); expectDeleteNodesVisitor(node, '[publicKeyTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); }); test('debugStringVisitor', () => { @@ -32,7 +32,7 @@ test('debugStringVisitor', () => { node, ` optionTypeNode [fixed] -| numberTypeNode [u64] +| integerTypeNode [u64] | publicKeyTypeNode`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/PostOffsetTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/PostOffsetTypeNode.test.ts deleted file mode 100644 index f332ddf50..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/PostOffsetTypeNode.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { postOffsetTypeNode, stringTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = postOffsetTypeNode(stringTypeNode('utf8'), 42); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 2); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[stringTypeNode]', null); - expectDeleteNodesVisitor(node, '[postOffsetTypeNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -postOffsetTypeNode [42.relative] -| stringTypeNode [utf8]`, - ); - - expectDebugStringVisitor( - postOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), - ` -postOffsetTypeNode [42.absolute] -| stringTypeNode [utf8]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/PreOffsetTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/PreOffsetTypeNode.test.ts deleted file mode 100644 index fd9570c79..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/PreOffsetTypeNode.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { preOffsetTypeNode, stringTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = preOffsetTypeNode(stringTypeNode('utf8'), 42); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 2); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[stringTypeNode]', null); - expectDeleteNodesVisitor(node, '[preOffsetTypeNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -preOffsetTypeNode [42.relative] -| stringTypeNode [utf8]`, - ); - - expectDebugStringVisitor( - preOffsetTypeNode(stringTypeNode('utf8'), 42, 'absolute'), - ` -preOffsetTypeNode [42.absolute] -| stringTypeNode [utf8]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/SizePrefixTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/SizePrefixTypeNode.test.ts deleted file mode 100644 index 214c11d43..000000000 --- a/packages/visitors-core/test/nodes/typeNodes/SizePrefixTypeNode.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { numberTypeNode, sizePrefixTypeNode, stringTypeNode } from '@codama/nodes'; -import { test } from 'vitest'; - -import { - expectDebugStringVisitor, - expectDeleteNodesVisitor, - expectIdentityVisitor, - expectMergeVisitorCount, -} from '../_setup'; - -const node = sizePrefixTypeNode(stringTypeNode('utf8'), numberTypeNode('u32')); - -test('mergeVisitor', () => { - expectMergeVisitorCount(node, 3); -}); - -test('identityVisitor', () => { - expectIdentityVisitor(node); -}); - -test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[sizePrefixTypeNode]', null); - expectDeleteNodesVisitor(node, '[stringTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); -}); - -test('debugStringVisitor', () => { - expectDebugStringVisitor( - node, - ` -sizePrefixTypeNode -| numberTypeNode [u32] -| stringTypeNode [utf8]`, - ); -}); diff --git a/packages/visitors-core/test/nodes/typeNodes/StructFieldTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/StructFieldTypeNode.test.ts index b63ba9d63..e74367234 100644 --- a/packages/visitors-core/test/nodes/typeNodes/StructFieldTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/StructFieldTypeNode.test.ts @@ -10,7 +10,7 @@ import { const node = structFieldTypeNode({ defaultValue: publicKeyValueNode('CzC5HidG6kR5J4haV7pKZmenYYVS7rw3SoBkqeStxZ9U'), - name: 'owner', + identifier: 'owner', type: publicKeyTypeNode(), }); diff --git a/packages/visitors-core/test/nodes/typeNodes/StructTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/StructTypeNode.test.ts index f4b265d37..2a55521fe 100644 --- a/packages/visitors-core/test/nodes/typeNodes/StructTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/StructTypeNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -9,8 +9,8 @@ import { } from '../_setup'; const node = structTypeNode([ - structFieldTypeNode({ name: 'owner', type: publicKeyTypeNode() }), - structFieldTypeNode({ name: 'amount', type: numberTypeNode('u64') }), + structFieldTypeNode({ identifier: 'owner', type: publicKeyTypeNode() }), + structFieldTypeNode({ identifier: 'amount', type: integerTypeNode('u64') }), ]); test('mergeVisitor', () => { @@ -24,7 +24,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[structTypeNode]', null); expectDeleteNodesVisitor(node, '[structFieldTypeNode]', { ...node, fields: undefined }); - expectDeleteNodesVisitor(node, ['[publicKeyTypeNode]', '[numberTypeNode]'], { ...node, fields: undefined }); + expectDeleteNodesVisitor(node, ['[publicKeyTypeNode]', '[integerTypeNode]'], { ...node, fields: undefined }); }); test('debugStringVisitor', () => { @@ -35,6 +35,6 @@ structTypeNode | structFieldTypeNode [owner] | | publicKeyTypeNode | structFieldTypeNode [amount] -| | numberTypeNode [u64]`, +| | integerTypeNode [u64]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/TupleTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/TupleTypeNode.test.ts index ed6a8db84..6fc0e86ad 100644 --- a/packages/visitors-core/test/nodes/typeNodes/TupleTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/TupleTypeNode.test.ts @@ -1,4 +1,4 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = tupleTypeNode([publicKeyTypeNode(), numberTypeNode('u64')]); +const node = tupleTypeNode([publicKeyTypeNode(), integerTypeNode('u64')]); test('mergeVisitor', () => { expectMergeVisitorCount(node, 3); @@ -20,7 +20,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[tupleTypeNode]', null); - expectDeleteNodesVisitor(node, ['[publicKeyTypeNode]', '[numberTypeNode]'], { ...node, items: undefined }); + expectDeleteNodesVisitor(node, ['[publicKeyTypeNode]', '[integerTypeNode]'], { ...node, items: undefined }); }); test('debugStringVisitor', () => { @@ -29,6 +29,6 @@ test('debugStringVisitor', () => { ` tupleTypeNode | publicKeyTypeNode -| numberTypeNode [u64]`, +| integerTypeNode [u64]`, ); }); diff --git a/packages/visitors-core/test/nodes/typeNodes/ZeroableOptionTypeNode.test.ts b/packages/visitors-core/test/nodes/typeNodes/ZeroableOptionTypeNode.test.ts index e7628ab41..fe944611a 100644 --- a/packages/visitors-core/test/nodes/typeNodes/ZeroableOptionTypeNode.test.ts +++ b/packages/visitors-core/test/nodes/typeNodes/ZeroableOptionTypeNode.test.ts @@ -8,7 +8,9 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = zeroableOptionTypeNode(publicKeyTypeNode(), constantValueNodeFromBytes('base16', 'ff'.repeat(32))); +const node = zeroableOptionTypeNode(publicKeyTypeNode(), { + zeroValue: constantValueNodeFromBytes('base16', 'ff'.repeat(32)), +}); test('mergeVisitor', () => { expectMergeVisitorCount(node, 5); diff --git a/packages/visitors-core/test/nodes/valueNodes/ConstantValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/ConstantValueNode.test.ts index 6f33fcf96..d6bd76df3 100644 --- a/packages/visitors-core/test/nodes/valueNodes/ConstantValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/ConstantValueNode.test.ts @@ -1,4 +1,4 @@ -import { constantValueNode, numberTypeNode, numberValueNode } from '@codama/nodes'; +import { constantValueNode, integerTypeNode, integerValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = constantValueNode(numberTypeNode('u8'), numberValueNode(42)); +const node = constantValueNode(integerTypeNode('u8'), integerValueNode('42')); test('mergeVisitor', () => { expectMergeVisitorCount(node, 3); @@ -20,8 +20,8 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[constantValueNode]', null); - expectDeleteNodesVisitor(node, '[numberTypeNode]', null); - expectDeleteNodesVisitor(node, '[numberValueNode]', null); + expectDeleteNodesVisitor(node, '[integerTypeNode]', null); + expectDeleteNodesVisitor(node, '[integerValueNode]', null); }); test('debugStringVisitor', () => { @@ -29,7 +29,7 @@ test('debugStringVisitor', () => { node, ` constantValueNode -| numberTypeNode [u8] -| numberValueNode [42]`, +| integerTypeNode [u8] +| integerValueNode [42]`, ); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/EnumValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/EnumValueNode.test.ts index ee2d959a6..74707f7a5 100644 --- a/packages/visitors-core/test/nodes/valueNodes/EnumValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/EnumValueNode.test.ts @@ -1,7 +1,7 @@ import { definedTypeLinkNode, enumValueNode, - numberValueNode, + integerValueNode, stringValueNode, structFieldValueNode, structValueNode, @@ -15,14 +15,12 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = enumValueNode( - definedTypeLinkNode('entity'), - 'person', - structValueNode([ +const node = enumValueNode(definedTypeLinkNode('entity'), 'person', { + value: structValueNode([ structFieldValueNode('name', stringValueNode('Alice')), - structFieldValueNode('age', numberValueNode(42)), + structFieldValueNode('age', integerValueNode('42')), ]), -); +}); test('mergeVisitor', () => { expectMergeVisitorCount(node, 7); @@ -48,6 +46,6 @@ enumValueNode [person] | | structFieldValueNode [name] | | | stringValueNode [Alice] | | structFieldValueNode [age] -| | | numberValueNode [42]`, +| | | integerValueNode [42]`, ); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/NumberValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/FloatValueNode.test.ts similarity index 66% rename from packages/visitors-core/test/nodes/valueNodes/NumberValueNode.test.ts rename to packages/visitors-core/test/nodes/valueNodes/FloatValueNode.test.ts index f77eace5b..2ecffd599 100644 --- a/packages/visitors-core/test/nodes/valueNodes/NumberValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/FloatValueNode.test.ts @@ -1,4 +1,4 @@ -import { numberValueNode } from '@codama/nodes'; +import { floatValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -8,7 +8,7 @@ import { expectMergeVisitorCount, } from '../_setup'; -const node = numberValueNode(42); +const node = floatValueNode('1.5'); test('mergeVisitor', () => { expectMergeVisitorCount(node, 1); @@ -19,9 +19,9 @@ test('identityVisitor', () => { }); test('deleteNodesVisitor', () => { - expectDeleteNodesVisitor(node, '[numberValueNode]', null); + expectDeleteNodesVisitor(node, '[floatValueNode]', null); }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `numberValueNode [42]`); + expectDebugStringVisitor(node, `floatValueNode [1.5]`); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/InjectedValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/InjectedValueNode.test.ts index 09fbd2995..5acce844f 100644 --- a/packages/visitors-core/test/nodes/valueNodes/InjectedValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/InjectedValueNode.test.ts @@ -23,5 +23,5 @@ test('deleteNodesVisitor', () => { }); test('debugStringVisitor', () => { - expectDebugStringVisitor(node, `injectedValueNode`); + expectDebugStringVisitor(node, `injectedValueNode [decimals]`); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/IntegerValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/IntegerValueNode.test.ts new file mode 100644 index 000000000..3e906f237 --- /dev/null +++ b/packages/visitors-core/test/nodes/valueNodes/IntegerValueNode.test.ts @@ -0,0 +1,27 @@ +import { integerValueNode } from '@codama/nodes'; +import { test } from 'vitest'; + +import { + expectDebugStringVisitor, + expectDeleteNodesVisitor, + expectIdentityVisitor, + expectMergeVisitorCount, +} from '../_setup'; + +const node = integerValueNode('42'); + +test('mergeVisitor', () => { + expectMergeVisitorCount(node, 1); +}); + +test('identityVisitor', () => { + expectIdentityVisitor(node); +}); + +test('deleteNodesVisitor', () => { + expectDeleteNodesVisitor(node, '[integerValueNode]', null); +}); + +test('debugStringVisitor', () => { + expectDebugStringVisitor(node, `integerValueNode [42]`); +}); diff --git a/packages/visitors-core/test/nodes/valueNodes/MapValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/MapValueNode.test.ts index 7f07b5d5a..b173ac891 100644 --- a/packages/visitors-core/test/nodes/valueNodes/MapValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/MapValueNode.test.ts @@ -1,4 +1,4 @@ -import { mapEntryValueNode, mapValueNode, numberValueNode, stringValueNode } from '@codama/nodes'; +import { mapEntryValueNode, mapValueNode, integerValueNode, stringValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -9,9 +9,9 @@ import { } from '../_setup'; const node = mapValueNode([ - mapEntryValueNode(stringValueNode('Alice'), numberValueNode(42)), - mapEntryValueNode(stringValueNode('Bob'), numberValueNode(37)), - mapEntryValueNode(stringValueNode('Carla'), numberValueNode(29)), + mapEntryValueNode(stringValueNode('Alice'), integerValueNode('42')), + mapEntryValueNode(stringValueNode('Bob'), integerValueNode('37')), + mapEntryValueNode(stringValueNode('Carla'), integerValueNode('29')), ]); test('mergeVisitor', () => { @@ -34,12 +34,12 @@ test('debugStringVisitor', () => { mapValueNode | mapEntryValueNode | | stringValueNode [Alice] -| | numberValueNode [42] +| | integerValueNode [42] | mapEntryValueNode | | stringValueNode [Bob] -| | numberValueNode [37] +| | integerValueNode [37] | mapEntryValueNode | | stringValueNode [Carla] -| | numberValueNode [29]`, +| | integerValueNode [29]`, ); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/PublicKeyValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/PublicKeyValueNode.test.ts index 58bd2fa53..148567360 100644 --- a/packages/visitors-core/test/nodes/valueNodes/PublicKeyValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/PublicKeyValueNode.test.ts @@ -25,7 +25,7 @@ test('deleteNodesVisitor', () => { test('debugStringVisitor', () => { expectDebugStringVisitor(node, `publicKeyValueNode [HqJgWgvkn5wMGU8LpzkRw8389N5Suvu2nZcmpya9JyJB]`); expectDebugStringVisitor( - publicKeyValueNode('HqJgWgvkn5wMGU8LpzkRw8389N5Suvu2nZcmpya9JyJB', 'myIdentifier'), + publicKeyValueNode('HqJgWgvkn5wMGU8LpzkRw8389N5Suvu2nZcmpya9JyJB', { identifier: 'myIdentifier' }), `publicKeyValueNode [myIdentifier.HqJgWgvkn5wMGU8LpzkRw8389N5Suvu2nZcmpya9JyJB]`, ); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/StructValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/StructValueNode.test.ts index ca31e186f..9fe807f4f 100644 --- a/packages/visitors-core/test/nodes/valueNodes/StructValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/StructValueNode.test.ts @@ -1,4 +1,4 @@ -import { numberValueNode, stringValueNode, structFieldValueNode, structValueNode } from '@codama/nodes'; +import { integerValueNode, stringValueNode, structFieldValueNode, structValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -10,7 +10,7 @@ import { const node = structValueNode([ structFieldValueNode('name', stringValueNode('Alice')), - structFieldValueNode('age', numberValueNode(42)), + structFieldValueNode('age', integerValueNode('42')), ]); test('mergeVisitor', () => { @@ -34,6 +34,6 @@ structValueNode | structFieldValueNode [name] | | stringValueNode [Alice] | structFieldValueNode [age] -| | numberValueNode [42]`, +| | integerValueNode [42]`, ); }); diff --git a/packages/visitors-core/test/nodes/valueNodes/TupleValueNode.test.ts b/packages/visitors-core/test/nodes/valueNodes/TupleValueNode.test.ts index 9c7f4028c..a309a8d8b 100644 --- a/packages/visitors-core/test/nodes/valueNodes/TupleValueNode.test.ts +++ b/packages/visitors-core/test/nodes/valueNodes/TupleValueNode.test.ts @@ -1,4 +1,4 @@ -import { numberValueNode, publicKeyValueNode, stringValueNode, tupleValueNode } from '@codama/nodes'; +import { integerValueNode, publicKeyValueNode, stringValueNode, tupleValueNode } from '@codama/nodes'; import { test } from 'vitest'; import { @@ -10,7 +10,7 @@ import { const node = tupleValueNode([ stringValueNode('Hello'), - numberValueNode(42), + integerValueNode('42'), publicKeyValueNode('9sL9D2kshFgZSHz98pUQxGphwVUbCNBGqhYGaWWNJags'), ]); @@ -24,7 +24,7 @@ test('identityVisitor', () => { test('deleteNodesVisitor', () => { expectDeleteNodesVisitor(node, '[tupleValueNode]', null); - expectDeleteNodesVisitor(node, ['[stringValueNode]', '[numberValueNode]', '[publicKeyValueNode]'], { + expectDeleteNodesVisitor(node, ['[stringValueNode]', '[integerValueNode]', '[publicKeyValueNode]'], { ...node, items: undefined, }); @@ -36,7 +36,7 @@ test('debugStringVisitor', () => { ` tupleValueNode | stringValueNode [Hello] -| numberValueNode [42] +| integerValueNode [42] | publicKeyValueNode [9sL9D2kshFgZSHz98pUQxGphwVUbCNBGqhYGaWWNJags]`, ); }); diff --git a/packages/visitors-core/test/nonNullableIdentityVisitor.test.ts b/packages/visitors-core/test/nonNullableIdentityVisitor.test.ts index 353c595fb..a64d9d349 100644 --- a/packages/visitors-core/test/nonNullableIdentityVisitor.test.ts +++ b/packages/visitors-core/test/nonNullableIdentityVisitor.test.ts @@ -1,11 +1,11 @@ -import { assertIsNode, Node, numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { assertIsNode, Node, integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { nonNullableIdentityVisitor, visit } from '../src'; test('it visits all nodes and returns different instances of the same nodes without returning null', () => { // Given the following 3-nodes tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // When we visit it using the non-nullable identity visitor. const result = visit(node, nonNullableIdentityVisitor()); diff --git a/packages/visitors-core/test/recordLinkablesVisitor.test.ts b/packages/visitors-core/test/recordLinkablesVisitor.test.ts index 736ed0675..291bc02ae 100644 --- a/packages/visitors-core/test/recordLinkablesVisitor.test.ts +++ b/packages/visitors-core/test/recordLinkablesVisitor.test.ts @@ -5,17 +5,15 @@ import { accountNode, definedTypeLinkNode, definedTypeNode, + identifierString, instructionAccountLinkNode, InstructionAccountNode, instructionAccountNode, - instructionArgumentLinkNode, - instructionArgumentNode, instructionLinkNode, instructionNode, + integerTypeNode, isNode, - numberTypeNode, pdaLinkNode, - camelCase, pdaNode, programLinkNode, programNode, @@ -35,9 +33,9 @@ import { test('it records program nodes', () => { // Given the following root node containing multiple program nodes. - const node = rootNode(programNode({ name: 'programA', publicKey: '1111' }), [ - programNode({ name: 'programB', publicKey: '2222' }), - ]); + const node = rootNode(programNode({ identifier: 'programA', publicKey: '1111' }), { + additionalPrograms: [programNode({ identifier: 'programB', publicKey: '2222' })], + }); // And a recordLinkablesOnFirstVisitVisitor extending any visitor. const linkables = new LinkableDictionary(); @@ -54,8 +52,8 @@ test('it records program nodes', () => { test('it records account nodes', () => { // Given the following program node containing multiple accounts nodes. const node = programNode({ - accounts: [accountNode({ name: 'accountA' }), accountNode({ name: 'accountB' })], - name: 'myProgram', + accounts: [accountNode({ identifier: 'accountA' }), accountNode({ identifier: 'accountB' })], + identifier: 'myProgram', publicKey: '1111', }); @@ -67,18 +65,24 @@ test('it records account nodes', () => { visit(node, visitor); // Then we expect account paths to be recorded and retrievable. - expect(linkables.getPath([accountLinkNode('accountA', 'myProgram')])).toEqual([node, (node.accounts ?? [])[0]]); - expect(linkables.getPath([accountLinkNode('accountB', 'myProgram')])).toEqual([node, (node.accounts ?? [])[1]]); + expect(linkables.getPath([accountLinkNode('accountA', { program: programLinkNode('myProgram') })])).toEqual([ + node, + (node.accounts ?? [])[0], + ]); + expect(linkables.getPath([accountLinkNode('accountB', { program: programLinkNode('myProgram') })])).toEqual([ + node, + (node.accounts ?? [])[1], + ]); }); test('it records defined type nodes', () => { // Given the following program node containing multiple defined type nodes. const node = programNode({ definedTypes: [ - definedTypeNode({ name: 'typeA', type: numberTypeNode('u32') }), - definedTypeNode({ name: 'typeB', type: numberTypeNode('u32') }), + definedTypeNode({ identifier: 'typeA', type: integerTypeNode('u32') }), + definedTypeNode({ identifier: 'typeB', type: integerTypeNode('u32') }), ], - name: 'myProgram', + identifier: 'myProgram', publicKey: '1111', }); @@ -90,11 +94,11 @@ test('it records defined type nodes', () => { visit(node, visitor); // Then we expect defined type paths to be recorded and retrievable. - expect(linkables.getPath([definedTypeLinkNode('typeA', 'myProgram')])).toEqual([ + expect(linkables.getPath([definedTypeLinkNode('typeA', { program: programLinkNode('myProgram') })])).toEqual([ node, (node.definedTypes ?? [])[0], ]); - expect(linkables.getPath([definedTypeLinkNode('typeB', 'myProgram')])).toEqual([ + expect(linkables.getPath([definedTypeLinkNode('typeB', { program: programLinkNode('myProgram') })])).toEqual([ node, (node.definedTypes ?? [])[1], ]); @@ -103,8 +107,8 @@ test('it records defined type nodes', () => { test('it records pda nodes', () => { // Given the following program node containing multiple pda nodes. const node = programNode({ - name: 'myProgram', - pdas: [pdaNode({ name: 'pdaA', seeds: [] }), pdaNode({ name: 'pdaB', seeds: [] })], + identifier: 'myProgram', + pdas: [pdaNode({ identifier: 'pdaA', seeds: [] }), pdaNode({ identifier: 'pdaB', seeds: [] })], publicKey: '1111', }); @@ -116,15 +120,24 @@ test('it records pda nodes', () => { visit(node, visitor); // Then we expect pda paths to be recorded and retrievable. - expect(linkables.getPath([pdaLinkNode('pdaA', 'myProgram')])).toEqual([node, (node.pdas ?? [])[0]]); - expect(linkables.getPath([pdaLinkNode('pdaB', 'myProgram')])).toEqual([node, (node.pdas ?? [])[1]]); + expect(linkables.getPath([pdaLinkNode('pdaA', { program: programLinkNode('myProgram') })])).toEqual([ + node, + (node.pdas ?? [])[0], + ]); + expect(linkables.getPath([pdaLinkNode('pdaB', { program: programLinkNode('myProgram') })])).toEqual([ + node, + (node.pdas ?? [])[1], + ]); }); test('it records instruction nodes', () => { // Given the following program node containing multiple instruction nodes. const node = programNode({ - instructions: [instructionNode({ name: 'instructionA' }), instructionNode({ name: 'instructionB' })], - name: 'myProgram', + identifier: 'myProgram', + instructions: [ + instructionNode({ identifier: 'instructionA' }), + instructionNode({ identifier: 'instructionB' }), + ], publicKey: '1111', }); @@ -136,25 +149,23 @@ test('it records instruction nodes', () => { visit(node, visitor); // Then we expect instruction paths to be recorded and retrievable. - expect(linkables.getPath([instructionLinkNode('instructionA', 'myProgram')])).toEqual([ - node, - (node.instructions ?? [])[0], - ]); - expect(linkables.getPath([instructionLinkNode('instructionB', 'myProgram')])).toEqual([ - node, - (node.instructions ?? [])[1], - ]); + expect(linkables.getPath([instructionLinkNode('instructionA', { program: programLinkNode('myProgram') })])).toEqual( + [node, (node.instructions ?? [])[0]], + ); + expect(linkables.getPath([instructionLinkNode('instructionB', { program: programLinkNode('myProgram') })])).toEqual( + [node, (node.instructions ?? [])[1]], + ); }); test('it records instruction account nodes', () => { // Given the following instruction node containing multiple accounts. const instructionAccounts = [ - instructionAccountNode({ isSigner: true, isWritable: false, name: 'accountA' }), - instructionAccountNode({ isSigner: false, isWritable: true, name: 'accountB' }), + instructionAccountNode({ identifier: 'accountA', isSigner: true, isWritable: false }), + instructionAccountNode({ identifier: 'accountB', isSigner: false, isWritable: true }), ]; const node = programNode({ - instructions: [instructionNode({ accounts: instructionAccounts, name: 'myInstruction' })], - name: 'myProgram', + identifier: 'myProgram', + instructions: [instructionNode({ accounts: instructionAccounts, identifier: 'myInstruction' })], publicKey: '1111', }); @@ -166,57 +177,24 @@ test('it records instruction account nodes', () => { visit(node, visitor); // Then we expect instruction account paths to be recorded and retrievable. - const instruction = instructionLinkNode('myInstruction', 'myProgram'); - expect(linkables.getPath([instructionAccountLinkNode('accountA', instruction)])).toEqual([ + const instruction = instructionLinkNode('myInstruction', { program: programLinkNode('myProgram') }); + expect(linkables.getPath([instructionAccountLinkNode('accountA', { instruction })])).toEqual([ node, (node.instructions ?? [])[0], instructionAccounts[0], ]); - expect(linkables.getPath([instructionAccountLinkNode('accountB', instruction)])).toEqual([ + expect(linkables.getPath([instructionAccountLinkNode('accountB', { instruction })])).toEqual([ node, (node.instructions ?? [])[0], instructionAccounts[1], ]); }); -test('it records instruction argument nodes', () => { - // Given the following instruction node containing multiple arguments. - const instructionArguments = [ - instructionArgumentNode({ name: 'argumentA', type: numberTypeNode('u32') }), - instructionArgumentNode({ name: 'argumentB', type: numberTypeNode('u32') }), - ]; - const node = programNode({ - instructions: [instructionNode({ arguments: instructionArguments, name: 'myInstruction' })], - name: 'myProgram', - publicKey: '1111', - }); - - // And a recordLinkablesOnFirstVisitVisitor extending any visitor. - const linkables = new LinkableDictionary(); - const visitor = recordLinkablesOnFirstVisitVisitor(voidVisitor(), linkables); - - // When we visit the tree. - visit(node, visitor); - - // Then we expect instruction argument paths to be recorded and retrievable. - const instruction = instructionLinkNode('myInstruction', 'myProgram'); - expect(linkables.getPath([instructionArgumentLinkNode('argumentA', instruction)])).toEqual([ - node, - (node.instructions ?? [])[0], - instructionArguments[0], - ]); - expect(linkables.getPath([instructionArgumentLinkNode('argumentB', instruction)])).toEqual([ - node, - (node.instructions ?? [])[0], - instructionArguments[1], - ]); -}); - test('it records all linkable before the first visit of the base visitor', () => { // Given the following root node with two programs. - const node = rootNode(programNode({ name: 'programA', publicKey: '1111' }), [ - programNode({ name: 'programB', publicKey: '2222' }), - ]); + const node = rootNode(programNode({ identifier: 'programA', publicKey: '1111' }), { + additionalPrograms: [programNode({ identifier: 'programB', publicKey: '2222' })], + }); // And a recordLinkablesOnFirstVisitVisitor extending a base visitor that // stores the linkable programs available at every visit. @@ -239,16 +217,16 @@ test('it records all linkable before the first visit of the base visitor', () => test('it keeps track of the current program when extending a visitor', () => { // Given the following root node containing two program containing an account with the same name. const programA = programNode({ - accounts: [accountNode({ name: 'someAccount' })], - name: 'programA', + accounts: [accountNode({ identifier: 'someAccount' })], + identifier: 'programA', publicKey: '1111', }); const programB = programNode({ - accounts: [accountNode({ name: 'someAccount' })], - name: 'programB', + accounts: [accountNode({ identifier: 'someAccount' })], + identifier: 'programB', publicKey: '2222', }); - const node = rootNode(programA, [programB]); + const node = rootNode(programA, { additionalPrograms: [programB] }); // And a recordLinkablesOnFirstVisitVisitor extending a base visitor that checks // the result of getting the linkable node with the same name for each program. @@ -258,7 +236,7 @@ test('it keeps track of the current program when extending a visitor', () => { const baseVisitor = interceptVisitor(voidVisitor(), (node, next) => { stack.push(node); if (isNode(node, 'programNode')) { - dictionary[node.name] = linkables.getOrThrow([...stack.getPath(), accountLinkNode('someAccount')]); + dictionary[node.identifier] = linkables.getOrThrow([...stack.getPath(), accountLinkNode('someAccount')]); } next(node); stack.pop(); @@ -275,20 +253,20 @@ test('it keeps track of the current program when extending a visitor', () => { test('it keeps track of the current instruction when extending a visitor', () => { // Given the following program node containing two instructions each containing an account with the same name. - const accountA = instructionAccountNode({ isSigner: true, isWritable: false, name: 'someAccount' }); - const accountB = instructionAccountNode({ isSigner: true, isWritable: false, name: 'someAccount' }); + const accountA = instructionAccountNode({ identifier: 'someAccount', isSigner: true, isWritable: false }); + const accountB = instructionAccountNode({ identifier: 'someAccount', isSigner: true, isWritable: false }); const node = programNode({ + identifier: 'myProgram', instructions: [ instructionNode({ accounts: [accountA], - name: 'instructionA', + identifier: 'instructionA', }), instructionNode({ accounts: [accountB], - name: 'instructionB', + identifier: 'instructionB', }), ], - name: 'myProgram', publicKey: '1111', }); @@ -300,7 +278,7 @@ test('it keeps track of the current instruction when extending a visitor', () => const baseVisitor = interceptVisitor(voidVisitor(), (node, next) => { stack.push(node); if (isNode(node, 'instructionNode')) { - dictionary[node.name] = linkables.getOrThrow([ + dictionary[node.identifier] = linkables.getOrThrow([ ...stack.getPath(), instructionAccountLinkNode('someAccount'), ]); @@ -320,7 +298,7 @@ test('it keeps track of the current instruction when extending a visitor', () => test('it does not record linkable types that are not under a program node', () => { // Given the following account node that is not under a program node. - const node = accountNode({ name: 'someAccount' }); + const node = accountNode({ identifier: 'someAccount' }); // And a recordLinkablesOnFirstVisitVisitor extending a void visitor. const linkables = new LinkableDictionary(); @@ -336,8 +314,8 @@ test('it does not record linkable types that are not under a program node', () = test('it can throw an exception when trying to retrieve a missing linked node', () => { // Given the following program node with one account. const node = programNode({ - accounts: [accountNode({ name: 'myAccount' })], - name: 'myProgram', + accounts: [accountNode({ identifier: 'myAccount' })], + identifier: 'myProgram', publicKey: '1111', }); @@ -347,7 +325,7 @@ test('it can throw an exception when trying to retrieve a missing linked node', visit(node, visitor); // When we try to retrieve a missing account node. - const linkNode = accountLinkNode('missingAccount', 'myProgram'); + const linkNode = accountLinkNode('missingAccount', { program: programLinkNode('myProgram') }); const getMissingAccount = () => linkables.getOrThrow([node, linkNode]); // Then we expect an exception to be thrown. @@ -355,7 +333,7 @@ test('it can throw an exception when trying to retrieve a missing linked node', new CodamaError(CODAMA_ERROR__LINKED_NODE_NOT_FOUND, { kind: 'accountLinkNode', linkNode, - name: camelCase('missingAccount'), + name: identifierString('missingAccount'), path: [node, linkNode], }), ); diff --git a/packages/visitors-core/test/recordNodeStackVisitor.test.ts b/packages/visitors-core/test/recordNodeStackVisitor.test.ts index 2bd712aeb..5a12e8993 100644 --- a/packages/visitors-core/test/recordNodeStackVisitor.test.ts +++ b/packages/visitors-core/test/recordNodeStackVisitor.test.ts @@ -1,4 +1,4 @@ -import { definedTypeNode, numberTypeNode, publicKeyTypeNode, TupleTypeNode, tupleTypeNode } from '@codama/nodes'; +import { definedTypeNode, integerTypeNode, publicKeyTypeNode, TupleTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { NodeStack, pipe, recordNodeStackVisitor, tapVisitor, visit, voidVisitor } from '../src'; @@ -6,8 +6,8 @@ import { NodeStack, pipe, recordNodeStackVisitor, tapVisitor, visit, voidVisitor test('it records the current node stack of a visit', () => { // Given the following tree. const node = definedTypeNode({ - name: 'myType', - type: tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]), + identifier: 'myType', + type: tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]), }); // And a visitor that records the current node stack and stores the number stacks in an array. @@ -16,7 +16,7 @@ test('it records the current node stack of a visit', () => { const visitor = pipe( voidVisitor(), v => recordNodeStackVisitor(v, stack), - v => tapVisitor(v, 'numberTypeNode', () => numberStacks.push(stack.clone())), + v => tapVisitor(v, 'integerTypeNode', () => numberStacks.push(stack.clone())), ); // When we visit the tree. @@ -33,8 +33,8 @@ test('it records the current node stack of a visit', () => { test('it includes the current node when applied last', () => { // Given the following tree. const node = definedTypeNode({ - name: 'myType', - type: tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]), + identifier: 'myType', + type: tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]), }); // And a visitor that records the current node stack as the last visitor modifier. @@ -42,7 +42,7 @@ test('it includes the current node when applied last', () => { const numberStacks: NodeStack[] = []; const visitor = pipe( voidVisitor(), - v => tapVisitor(v, 'numberTypeNode', () => numberStacks.push(stack.clone())), + v => tapVisitor(v, 'integerTypeNode', () => numberStacks.push(stack.clone())), v => recordNodeStackVisitor(v, stack), ); diff --git a/packages/visitors-core/test/recordProvidedScopeVisitor.test.ts b/packages/visitors-core/test/recordProvidedScopeVisitor.test.ts new file mode 100644 index 000000000..b50ed4ce6 --- /dev/null +++ b/packages/visitors-core/test/recordProvidedScopeVisitor.test.ts @@ -0,0 +1,103 @@ +import { + identifierString, + injectedValueNode, + instructionNode, + integerValueNode, + isNode, + providedNode, + structTypeNode, +} from '@codama/nodes'; +import { expect, test } from 'vitest'; + +import { interceptVisitor, pipe, ProvidedScope, recordProvidedScopeVisitor, visit, voidVisitor } from '../src'; + +test('it opens the frame of an instruction for the duration of its visit', () => { + // Given an instruction providing a key. + const node = instructionNode({ + identifier: 'myInstruction', + provides: [providedNode('decimals', integerValueNode('6'))], + }); + + // And a visitor recording the resolved key when visiting the instruction. + const scope = new ProvidedScope(); + const resolvedDuringVisit: unknown[] = []; + const visitor = pipe( + voidVisitor(), + v => + interceptVisitor(v, (visited, next) => { + if (isNode(visited, 'instructionNode')) { + resolvedDuringVisit.push( + scope.resolve(injectedValueNode({ key: 'decimals' }), { kinds: ['integerValueNode'] }), + ); + } + return next(visited); + }), + v => recordProvidedScopeVisitor(v, scope), + ); + + // When we visit it. + visit(node, visitor); + + // Then the key was provided during the visit and the frame is closed afterwards. + expect(resolvedDuringVisit).toEqual([integerValueNode('6')]); + expect(scope.get(identifierString('decimals'))).toBeUndefined(); +}); + +test('it lets a sub-instruction shadow its parent', () => { + // Given a parent and a sub-instruction providing the same key. + const node = instructionNode({ + identifier: 'parent', + provides: [providedNode('decimals', integerValueNode('6'))], + subInstructions: [ + instructionNode({ + identifier: 'child', + provides: [providedNode('decimals', integerValueNode('9'))], + }), + ], + }); + + // And a visitor recording the resolved key for each instruction. + const scope = new ProvidedScope(); + const resolved: Record = {}; + const visitor = pipe( + voidVisitor(), + v => + interceptVisitor(v, (visited, next) => { + if (isNode(visited, 'instructionNode')) { + const result = next(visited); + resolved[visited.identifier] = scope.resolve(injectedValueNode({ key: 'decimals' }), { + kinds: ['integerValueNode'], + }); + return result; + } + return next(visited); + }), + v => recordProvidedScopeVisitor(v, scope), + ); + + // When we visit the tree. + visit(node, visitor); + + // Then each instruction sees its own provider. + expect(resolved).toEqual({ child: integerValueNode('9'), parent: integerValueNode('6') }); +}); + +test('it only opens frames for instructions that provide values', () => { + // Given a scope counting the frames it opens. + class CountingScope extends ProvidedScope { + pushes = 0; + override push(...args: Parameters): void { + this.pushes++; + super.push(...args); + } + } + const scope = new CountingScope(); + const visitor = recordProvidedScopeVisitor(voidVisitor(), scope); + + // When we visit a non-instruction node and an instruction without `provides`. + visit(structTypeNode([]), visitor); + visit(instructionNode({ data: structTypeNode([]), identifier: 'myInstruction' }), visitor); + + // Then no frame was opened. + expect(scope.pushes).toBe(0); +}); diff --git a/packages/visitors-core/test/removeDocsVisitor.test.ts b/packages/visitors-core/test/removeDocsVisitor.test.ts index a0fa943c2..46917fb3a 100644 --- a/packages/visitors-core/test/removeDocsVisitor.test.ts +++ b/packages/visitors-core/test/removeDocsVisitor.test.ts @@ -1,48 +1,45 @@ -import { numberTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, structFieldTypeNode, structTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { removeDocsVisitor, visit } from '../src'; -test('it empties the docs array of any node that contains docs', () => { +test('it removes the docs from any node that contains docs', () => { // Given the following struct node with docs. const node = structTypeNode([ structFieldTypeNode({ - docs: ['The owner of the account.'], - name: 'owner', + docs: 'The owner of the account.', + identifier: 'owner', type: publicKeyTypeNode(), }), structFieldTypeNode({ - docs: ['The wallet allowed to modify the account.'], - name: 'authority', + docs: 'The wallet allowed to modify the account.', + identifier: 'authority', type: publicKeyTypeNode(), }), structFieldTypeNode({ - docs: ['The amount of tokens in basis points.'], - name: 'amount', - type: numberTypeNode('u64'), + docs: 'The amount of tokens in basis points.', + identifier: 'amount', + type: integerTypeNode('u64'), }), ]); // When we remove the docs from the node. const result = visit(node, removeDocsVisitor()); - // Then we expect the following node. + // Then we expect the following node with no docs. expect(result).toEqual( structTypeNode([ structFieldTypeNode({ - docs: [], - name: 'owner', + identifier: 'owner', type: publicKeyTypeNode(), }), structFieldTypeNode({ - docs: [], - name: 'authority', + identifier: 'authority', type: publicKeyTypeNode(), }), structFieldTypeNode({ - docs: [], - name: 'amount', - type: numberTypeNode('u64'), + identifier: 'amount', + type: integerTypeNode('u64'), }), ]), ); @@ -52,8 +49,8 @@ test('it freezes the returned node', () => { // Given the following struct node with docs. const node = structTypeNode([ structFieldTypeNode({ - docs: ['The owner of the account.'], - name: 'owner', + docs: 'The owner of the account.', + identifier: 'owner', type: publicKeyTypeNode(), }), ]); @@ -69,8 +66,8 @@ test('it can create partial visitors', () => { // Given the following struct node with docs. const node = structTypeNode([ structFieldTypeNode({ - docs: ['The owner of the account.'], - name: 'owner', + docs: 'The owner of the account.', + identifier: 'owner', type: publicKeyTypeNode(), }), ]); diff --git a/packages/visitors-core/test/singleNodeVisitor.test.ts b/packages/visitors-core/test/singleNodeVisitor.test.ts index ce0231c01..f2df09c46 100644 --- a/packages/visitors-core/test/singleNodeVisitor.test.ts +++ b/packages/visitors-core/test/singleNodeVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, ProgramNode, publicKeyTypeNode, rootNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, ProgramNode, publicKeyTypeNode, rootNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { rootNodeVisitor, singleNodeVisitor, visit } from '../src'; test('it visits a single node and return a custom value', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a visitor that counts the number of direct items in a tuple node. const visitor = singleNodeVisitor('tupleTypeNode', node => (node.items ?? []).length); @@ -18,7 +18,7 @@ test('it visits a single node and return a custom value', () => { // And no other nodes can be visited. // @ts-expect-error NumberTypeNode is not supported. - expect(() => visit(numberTypeNode('u64'), visitor)).toThrow(); + expect(() => visit(integerTypeNode('u64'), visitor)).toThrow(); // @ts-expect-error PublicKeyTypeNode is not supported. expect(() => visit(publicKeyTypeNode(), visitor)).toThrow(); }); @@ -29,7 +29,9 @@ test('it can create rootNode only visitors that return new rootNode instances', // And a root node visitor that adds an additional program node. const visitor = rootNodeVisitor(node => - rootNode(node.program, [...(node.additionalPrograms ?? []), {} as ProgramNode]), + rootNode(node.program, { + additionalPrograms: [...(node.additionalPrograms ?? []), {} as ProgramNode], + }), ); // When we visit the empty root node using that visitor. diff --git a/packages/visitors-core/test/staticVisitor.test.ts b/packages/visitors-core/test/staticVisitor.test.ts index 910a5074f..c89df1e06 100644 --- a/packages/visitors-core/test/staticVisitor.test.ts +++ b/packages/visitors-core/test/staticVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { staticVisitor, visit } from '../src'; test('it returns the same value for any visited node', () => { // Given the following 3-nodes tree. - const item0 = numberTypeNode('u32'); + const item0 = integerTypeNode('u32'); const item1 = publicKeyTypeNode(); const node = tupleTypeNode([item0, item1]); @@ -14,21 +14,21 @@ test('it returns the same value for any visited node', () => { // Then we expect the following results when visiting different nodes. expect(visit(node, visitor)).toBe('tupleTypeNode'); - expect(visit(item0, visitor)).toBe('numberTypeNode'); + expect(visit(item0, visitor)).toBe('integerTypeNode'); expect(visit(item1, visitor)).toBe('publicKeyTypeNode'); }); test('it can create partial visitor', () => { // Given the following 3-nodes tree. - const item0 = numberTypeNode('u32'); + const item0 = integerTypeNode('u32'); const node = tupleTypeNode([item0, publicKeyTypeNode()]); // And a static visitor that supports only 2 of these nodes. - const visitor = staticVisitor(node => node.kind, { keys: ['tupleTypeNode', 'numberTypeNode'] }); + const visitor = staticVisitor(node => node.kind, { keys: ['tupleTypeNode', 'integerTypeNode'] }); // Then we expect the following results when visiting supported nodes. expect(visit(node, visitor)).toBe('tupleTypeNode'); - expect(visit(item0, visitor)).toBe('numberTypeNode'); + expect(visit(item0, visitor)).toBe('integerTypeNode'); // But expect an error when visiting an unsupported node. // @ts-expect-error PublicKeyTypeNode is not supported. diff --git a/packages/visitors-core/test/tapVisitor.test.ts b/packages/visitors-core/test/tapVisitor.test.ts index 9d6ca240d..62fee865f 100644 --- a/packages/visitors-core/test/tapVisitor.test.ts +++ b/packages/visitors-core/test/tapVisitor.test.ts @@ -1,11 +1,11 @@ -import { NumberTypeNode, numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { IntegerTypeNode, integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { mergeVisitor, tapVisitor, visit } from '../src'; test('it returns a new instance of the same visitor whilst tapping into one of its visits', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a sum visitor that counts the nodes. const visitor = mergeVisitor( @@ -13,10 +13,10 @@ test('it returns a new instance of the same visitor whilst tapping into one of i (_, values) => values.reduce((a, b) => a + b, 1), ); - // And a tap visitor that taps into the numberTypeNode visit and counts them. + // And a tap visitor that taps into the integerTypeNode visit and counts them. let numberOfNumberNodes = 0; - const tappedVisitor = tapVisitor(visitor, 'numberTypeNode', node => { - node satisfies NumberTypeNode; + const tappedVisitor = tapVisitor(visitor, 'integerTypeNode', node => { + node satisfies IntegerTypeNode; numberOfNumberNodes++; }); diff --git a/packages/visitors-core/test/topDownTransformerVisitor.test.ts b/packages/visitors-core/test/topDownTransformerVisitor.test.ts index 508114fc1..870a04c16 100644 --- a/packages/visitors-core/test/topDownTransformerVisitor.test.ts +++ b/packages/visitors-core/test/topDownTransformerVisitor.test.ts @@ -2,7 +2,7 @@ import { assertIsNode, definedTypeNode, isNode, - numberTypeNode, + integerTypeNode, programNode, publicKeyTypeNode, tupleTypeNode, @@ -19,11 +19,11 @@ import { test('it can transform nodes to the same kind of node', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that transforms all number nodes into u64 number nodes. const visitor = topDownTransformerVisitor([ - node => (isNode(node, 'numberTypeNode') ? (numberTypeNode('u64') as typeof node) : node), + node => (isNode(node, 'integerTypeNode') ? (integerTypeNode('u64') as typeof node) : node), ]); // When we visit the tree using that visitor. @@ -31,19 +31,19 @@ test('it can transform nodes to the same kind of node', () => { // Then we expect the number nodes to have been transformed into u64 number nodes. expect(result).toEqual( - tupleTypeNode([numberTypeNode('u64'), tupleTypeNode([numberTypeNode('u64'), publicKeyTypeNode()])]), + tupleTypeNode([integerTypeNode('u64'), tupleTypeNode([integerTypeNode('u64'), publicKeyTypeNode()])]), ); }); test('it can transform nodes using node selectors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that uses a node selector to select all number nodes. const visitor = topDownTransformerVisitor([ { - select: '[numberTypeNode]', - transform: _node => numberTypeNode('u64') as typeof _node, + select: '[integerTypeNode]', + transform: _node => integerTypeNode('u64') as typeof _node, }, ]); @@ -52,13 +52,13 @@ test('it can transform nodes using node selectors', () => { // Then we expect the number nodes to have been transformed into u64 number nodes. expect(result).toEqual( - tupleTypeNode([numberTypeNode('u64'), tupleTypeNode([numberTypeNode('u64'), publicKeyTypeNode()])]), + tupleTypeNode([integerTypeNode('u64'), tupleTypeNode([integerTypeNode('u64'), publicKeyTypeNode()])]), ); }); test('it can create partial transformer visitors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a tuple-only transformer visitor that prefixes all tuples with another number node. const visitor = topDownTransformerVisitor( @@ -67,7 +67,7 @@ test('it can create partial transformer visitors', () => { select: '[tupleTypeNode]', transform: node => { assertIsNode(node, 'tupleTypeNode'); - return tupleTypeNode([numberTypeNode('u64'), ...(node.items ?? [])]) as unknown as typeof node; + return tupleTypeNode([integerTypeNode('u64'), ...(node.items ?? [])]) as unknown as typeof node; }, }, ], @@ -80,25 +80,25 @@ test('it can create partial transformer visitors', () => { // Then we expect the following tree. expect(result).toEqual( tupleTypeNode([ - numberTypeNode('u64'), - numberTypeNode('u32'), - tupleTypeNode([numberTypeNode('u64'), numberTypeNode('u32'), publicKeyTypeNode()]), + integerTypeNode('u64'), + integerTypeNode('u32'), + tupleTypeNode([integerTypeNode('u64'), integerTypeNode('u32'), publicKeyTypeNode()]), ]), ); // And the other nodes cannot be visited. // @ts-expect-error NumberTypeNode is not a tuple node. - expect(() => visit(numberTypeNode('u64'), visitor)).toThrow(); + expect(() => visit(integerTypeNode('u64'), visitor)).toThrow(); // @ts-expect-error PublicKeyTypeNode is not a tuple node. expect(() => visit(publicKeyTypeNode(), visitor)).toThrow(); }); test('it can be used to delete nodes', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that deletes all number nodes. - const visitor = topDownTransformerVisitor([{ select: '[numberTypeNode]', transform: () => null }]); + const visitor = topDownTransformerVisitor([{ select: '[integerTypeNode]', transform: () => null }]); // When we visit the tree using that visitor. const result = visit(node, visitor); @@ -109,15 +109,15 @@ test('it can be used to delete nodes', () => { test('it can transform nodes using multiple node selectors', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a transformer visitor that uses two node selectors such that // - the first one selects all number nodes, and // - the second one selects all nodes with more than one ancestor. const visitor = topDownTransformerVisitor([ { - select: ['[numberTypeNode]', path => path.length > 2], - transform: _node => numberTypeNode('u64') as typeof _node, + select: ['[integerTypeNode]', path => path.length > 2], + transform: _node => integerTypeNode('u64') as typeof _node, }, ]); @@ -126,24 +126,24 @@ test('it can transform nodes using multiple node selectors', () => { // Then we expect both node selectors to have been applied. expect(result).toEqual( - tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u64'), publicKeyTypeNode()])]), + tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u64'), publicKeyTypeNode()])]), ); }); test('it can start from an existing stack', () => { // Given the following tuple node inside a program node. - const tuple = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); - const myTuple = definedTypeNode({ name: 'myTuple', type: tuple }); + const tuple = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); + const myTuple = definedTypeNode({ identifier: 'myTuple', type: tuple }); const program = programNode({ definedTypes: [myTuple], - name: 'myProgram', + identifier: 'myProgram', publicKey: '1111', }); // And a transformer that removes all number nodes // from programs whose public key is '1111'. const transformer: TopDownNodeTransformerWithSelector = { - select: ['[numberTypeNode]', path => findProgramNodeFromPath(path)?.publicKey === '1111'], + select: ['[integerTypeNode]', path => findProgramNodeFromPath(path)?.publicKey === '1111'], transform: () => null, }; diff --git a/packages/visitors-core/test/visitor.test.ts b/packages/visitors-core/test/visitor.test.ts index bebe5e801..0b5f09fb4 100644 --- a/packages/visitors-core/test/visitor.test.ts +++ b/packages/visitors-core/test/visitor.test.ts @@ -1,6 +1,6 @@ import { - NumberTypeNode, - numberTypeNode, + IntegerTypeNode, + integerTypeNode, PublicKeyTypeNode, publicKeyTypeNode, TupleTypeNode, @@ -12,18 +12,18 @@ import { visit, Visitor, visitOrElse } from '../src'; test('it can create visitors as plain objects', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a plain object visitor that counts the nodes with different weights. - const visitor: Visitor = { - visitNumberType() { + const visitor: Visitor = { + visitIntegerType() { return 1; }, visitPublicKeyType() { return 2; }, visitTupleType(node) { - const castedChildren = node.items as (NumberTypeNode | PublicKeyTypeNode | TupleTypeNode)[]; + const castedChildren = node.items as (IntegerTypeNode | PublicKeyTypeNode | TupleTypeNode)[]; return castedChildren.map(child => visit(child, this)).reduce((a, b) => a + b, 10); }, }; @@ -37,12 +37,12 @@ test('it can create visitors as plain objects', () => { test('it can use visitOrElse to fallback if a nested node is not supported by the visitor', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()]); + const node = tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()]); // And a plain object visitor that counts the tuples and numbers nodes only // Such that it falls back to 42 for any other node. - const visitor: Visitor = { - visitNumberType() { + const visitor: Visitor = { + visitIntegerType() { return 1; }, visitTupleType(node) { diff --git a/packages/visitors-core/test/voidVisitor.test.ts b/packages/visitors-core/test/voidVisitor.test.ts index 41cf853d9..01be402fb 100644 --- a/packages/visitors-core/test/voidVisitor.test.ts +++ b/packages/visitors-core/test/voidVisitor.test.ts @@ -1,11 +1,11 @@ -import { numberTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; +import { integerTypeNode, publicKeyTypeNode, tupleTypeNode } from '@codama/nodes'; import { expect, test } from 'vitest'; import { extendVisitor, visit, voidVisitor } from '../src'; test('it visits all nodes and returns void', () => { // Given the following tree. - const node = tupleTypeNode([numberTypeNode('u32'), tupleTypeNode([numberTypeNode('u32'), publicKeyTypeNode()])]); + const node = tupleTypeNode([integerTypeNode('u32'), tupleTypeNode([integerTypeNode('u32'), publicKeyTypeNode()])]); // And a void visitor extended such that it counts the tuple nodes. let counter = 0;