diff --git a/.changeset/dirty-onions-arrive.md b/.changeset/dirty-onions-arrive.md new file mode 100644 index 000000000..4d57b0495 --- /dev/null +++ b/.changeset/dirty-onions-arrive.md @@ -0,0 +1,6 @@ +--- +'@codama/renderers-js-umi': minor +'@codama/renderers-js': minor +--- + +Fix LinkNode paths for JavaScript `getTypeManifestVisitors` diff --git a/packages/renderers-js-umi/src/getRenderMapVisitor.ts b/packages/renderers-js-umi/src/getRenderMapVisitor.ts index a3862a9c8..1d4bb4e96 100644 --- a/packages/renderers-js-umi/src/getRenderMapVisitor.ts +++ b/packages/renderers-js-umi/src/getRenderMapVisitor.ts @@ -96,6 +96,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor< linkables, nonScalarEnums, parentName, + stack, }); const typeManifestVisitor = getTypeManifestVisitor(); const resolvedInstructionInputVisitor = getResolvedInstructionInputsVisitor(); diff --git a/packages/renderers-js-umi/src/getTypeManifestVisitor.ts b/packages/renderers-js-umi/src/getTypeManifestVisitor.ts index 885317fb7..8e392eaa5 100644 --- a/packages/renderers-js-umi/src/getTypeManifestVisitor.ts +++ b/packages/renderers-js-umi/src/getTypeManifestVisitor.ts @@ -64,11 +64,12 @@ export function getTypeManifestVisitor(input: { linkables: LinkableDictionary; nonScalarEnums: CamelCaseString[]; parentName?: { loose: string; strict: string }; + stack?: NodeStack; }) { const { linkables, nonScalarEnums, customAccountData, customInstructionData, getImportFrom } = input; let parentName = input.parentName ?? null; let parentSize: NumberTypeNode | number | null = null; - const stack = new NodeStack(); + const stack = input.stack ?? new NodeStack(); return pipe( staticVisitor( @@ -428,7 +429,6 @@ export function getTypeManifestVisitor(input: { const variantName = pascalCase(node.variant); const importFrom = getImportFrom(node.enum); - // FIXME(loris): No program node can ever be in this stack. const enumNode = linkables.get([...stack.getPath(), node.enum])?.type; const isScalar = enumNode && isNode(enumNode, 'enumTypeNode') diff --git a/packages/renderers-js/src/getRenderMapVisitor.ts b/packages/renderers-js/src/getRenderMapVisitor.ts index 0ed1dff28..14d9ed3e5 100644 --- a/packages/renderers-js/src/getRenderMapVisitor.ts +++ b/packages/renderers-js/src/getRenderMapVisitor.ts @@ -114,6 +114,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { nameApi, nonScalarEnums, parentName, + stack, }); const typeManifestVisitor = getTypeManifestVisitor(); const resolvedInstructionInputVisitor = getResolvedInstructionInputsVisitor(); diff --git a/packages/renderers-js/src/getTypeManifestVisitor.ts b/packages/renderers-js/src/getTypeManifestVisitor.ts index 7390175fc..d6f7e72c4 100644 --- a/packages/renderers-js/src/getTypeManifestVisitor.ts +++ b/packages/renderers-js/src/getTypeManifestVisitor.ts @@ -41,9 +41,10 @@ export function getTypeManifestVisitor(input: { nameApi: NameApi; nonScalarEnums: CamelCaseString[]; parentName?: { loose: string; strict: string }; + stack?: NodeStack; }) { const { nameApi, linkables, nonScalarEnums, customAccountData, customInstructionData, getImportFrom } = input; - const stack = new NodeStack(); + const stack = input.stack ?? new NodeStack(); let parentName = input.parentName ?? null; return pipe( @@ -355,7 +356,6 @@ export function getTypeManifestVisitor(input: { const enumFunction = nameApi.discriminatedUnionFunction(node.enum.name); const importFrom = getImportFrom(node.enum); - // FIXME(loris): No program node can ever be in this stack. const enumNode = linkables.get([...stack.getPath(), node.enum])?.type; const isScalar = enumNode && isNode(enumNode, 'enumTypeNode') diff --git a/packages/renderers-js/test/links/definedType.test.ts b/packages/renderers-js/test/links/definedType.test.ts index de32739a9..ce74ed581 100644 --- a/packages/renderers-js/test/links/definedType.test.ts +++ b/packages/renderers-js/test/links/definedType.test.ts @@ -1,11 +1,17 @@ import { definedTypeLinkNode, definedTypeNode, + enumEmptyVariantTypeNode, + enumTupleVariantTypeNode, + enumTypeNode, + enumValueNode, fixedSizeTypeNode, + numberTypeNode, programNode, stringTypeNode, structFieldTypeNode, structTypeNode, + tupleTypeNode, } from '@codama/nodes'; import { visit } from '@codama/visitors-core'; import { test } from 'vitest'; @@ -91,3 +97,95 @@ test('it can override the import of a linked type', async () => { '../../hooked': ['Symbol', 'SymbolArgs', 'getSymbolEncoder', 'getSymbolDecoder'], }); }); + +test('it knows if an enum value is a scalar enum using link nodes', async () => { + // Given a program with a scalar enum linked in a default value. + const node = programNode({ + definedTypes: [ + definedTypeNode({ + name: 'person', + type: structTypeNode([ + structFieldTypeNode({ + defaultValue: enumValueNode('direction', 'up'), + name: 'movement', + type: definedTypeLinkNode('direction'), + }), + ]), + }), + definedTypeNode({ + name: 'direction', + type: enumTypeNode([ + enumEmptyVariantTypeNode('up'), + enumEmptyVariantTypeNode('right'), + enumEmptyVariantTypeNode('down'), + enumEmptyVariantTypeNode('left'), + ]), + }), + ], + name: 'myProgram', + publicKey: '1111', + }); + + // When we render it. + const renderMap = visit(node, getRenderMapVisitor()); + + // Then we expect the direction enum to be exported as a scalar enum. + await renderMapContains(renderMap, 'types/person.ts', [ + 'movement: value.movement ?? Direction.Up', + 'export type Person = { movement: Direction }', + 'export type PersonArgs = { movement?: DirectionArgs }', + 'getDirectionEncoder()', + 'getDirectionDecoder()', + ]); + + // And we expect the following imports. + await renderMapContainsImports(renderMap, 'types/person.ts', { + '.': ['Direction', 'DirectionArgs', 'getDirectionEncoder', 'getDirectionDecoder'], + }); +}); + +test('it knows if an enum value is a data enum using link nodes', async () => { + // Given a program with a data enum linked in a default value. + const node = programNode({ + definedTypes: [ + definedTypeNode({ + name: 'person', + type: structTypeNode([ + structFieldTypeNode({ + defaultValue: enumValueNode('action', 'stop'), + name: 'nextAction', + type: definedTypeLinkNode('action'), + }), + ]), + }), + definedTypeNode({ + name: 'action', + type: enumTypeNode([ + enumEmptyVariantTypeNode('stop'), + enumEmptyVariantTypeNode('turnRight'), + enumEmptyVariantTypeNode('turnLeft'), + enumTupleVariantTypeNode('moveForward', tupleTypeNode([numberTypeNode('u8')])), + ]), + }), + ], + name: 'myProgram', + publicKey: '1111', + }); + + // When we render it. + const renderMap = visit(node, getRenderMapVisitor()); + + // Then we expect the action enum to be exported as a data enum. + await renderMapContains(renderMap, 'types/person.ts', [ + "nextAction: value.nextAction ?? action('Stop')", + 'export type Person = { nextAction: Action }', + 'export type PersonArgs = { nextAction?: ActionArgs }', + 'getActionEncoder()', + 'getActionDecoder()', + ]); + + // And we expect the following imports. + await renderMapContainsImports(renderMap, 'types/person.ts', { + '.': ['Action', 'ActionArgs', 'getActionEncoder', 'getActionDecoder'], + }); +});