From 46d9231e16c504fe9e8bf8c4630283e7ab48ea2a Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Mon, 27 Jul 2026 13:26:04 +0100 Subject: [PATCH] Support `@codama/nodes@1.10` optional array attributes This PR upgrades the `@codama/*` dependencies to 1.10 and adapts the renderer to `@codama/nodes@1.10`, whose node array attributes are now optional (`Array | undefined`). Array reads are guarded with `?? []` throughout, and the new `injectedValueNode` value kind now throws an explicit unsupported-node error. --- .changeset/humble-singers-battle.md | 5 ++ package.json | 10 +-- pnpm-lock.yaml | 104 ++++++++++++++-------------- src/getRenderMapVisitor.ts | 20 +++--- src/getTypeManifestVisitor.ts | 6 +- src/renderValueNodeVisitor.ts | 15 ++-- 6 files changed, 85 insertions(+), 75 deletions(-) create mode 100644 .changeset/humble-singers-battle.md diff --git a/.changeset/humble-singers-battle.md b/.changeset/humble-singers-battle.md new file mode 100644 index 0000000..1adbc51 --- /dev/null +++ b/.changeset/humble-singers-battle.md @@ -0,0 +1,5 @@ +--- +'@codama/renderers-rust': patch +--- + +Support `@codama/nodes@1.10`, whose node array attributes are now optional (`Array | undefined`). Array reads are guarded with `?? []` throughout the renderer, and the new `injectedValueNode` value kind now throws an explicit unsupported-node error rather than being silently mishandled. diff --git a/package.json b/package.json index e9c7e8a..1372de1 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,10 @@ "test:unit": "vitest run" }, "dependencies": { - "@codama/errors": "^1.8.0", - "@codama/nodes": "^1.8.0", - "@codama/renderers-core": "^1.3.9", - "@codama/visitors-core": "^1.8.0", + "@codama/errors": "^1.10.0", + "@codama/nodes": "^1.10.0", + "@codama/renderers-core": "^1.3.11", + "@codama/visitors-core": "^1.10.0", "@iarna/toml": "^2.2.5", "@solana/codecs-strings": "^6.0.0", "nunjucks": "^3.2.4", @@ -53,7 +53,7 @@ "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@changesets/cli": "^2.29.7", - "@codama/nodes-from-anchor": "^1.5.1", + "@codama/nodes-from-anchor": "^1.5.3", "@solana/eslint-config-solana": "^6.0.1", "@solana/prettier-config-solana": "0.0.5", "@types/node": "^26", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d948148..45ba387 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,17 +9,17 @@ importers: .: dependencies: '@codama/errors': - specifier: ^1.8.0 - version: 1.8.0 + specifier: ^1.10.0 + version: 1.10.0 '@codama/nodes': - specifier: ^1.8.0 - version: 1.8.0 + specifier: ^1.10.0 + version: 1.10.0 '@codama/renderers-core': - specifier: ^1.3.9 - version: 1.3.9 + specifier: ^1.3.11 + version: 1.3.11 '@codama/visitors-core': - specifier: ^1.8.0 - version: 1.8.0 + specifier: ^1.10.0 + version: 1.10.0 '@iarna/toml': specifier: ^2.2.5 version: 2.2.5 @@ -40,8 +40,8 @@ importers: specifier: ^2.29.7 version: 2.31.1(@types/node@26.1.1) '@codama/nodes-from-anchor': - specifier: ^1.5.1 - version: 1.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) + specifier: ^1.5.3 + version: 1.5.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) '@solana/eslint-config-solana': specifier: ^6.0.1 version: 6.0.1(@eslint/js@9.39.1)(@types/eslint@9.6.1)(@types/eslint__js@8.42.3)(eslint-plugin-jest@29.0.1(@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(jest@30.1.3(@types/node@26.1.1))(typescript@5.9.3))(eslint-plugin-react-hooks@5.2.0(eslint@9.39.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.1))(eslint-plugin-sort-keys-fix@1.1.2)(eslint-plugin-typescript-sort-keys@3.3.0(@typescript-eslint/parser@8.43.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(globals@14.0.0)(jest@30.1.3(@types/node@26.1.1))(typescript-eslint@8.43.0(eslint@9.39.1)(typescript@5.9.3))(typescript@5.9.3) @@ -314,30 +314,30 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@codama/errors@1.8.0': - resolution: {integrity: sha512-A0FNhbfS1PBSK0nS+g65IR8+OKkifuIJCCnLCUBXb+I/OYMGlJMycMpU4pwAEEGS5GAnue7D/llXE+KpUaMi8w==} + '@codama/errors@1.10.0': + resolution: {integrity: sha512-/qiWQcbPIsyFlP1LrLqJfHt1QNbkY2wtC4UxZyKLnuZtc62YPlgDrN2wp4wcfRuE/DC0n3Y6ZSwYUrDBZyAnvA==} hasBin: true - '@codama/fragments@0.1.1': - resolution: {integrity: sha512-+DeC2YklyYf+qjRkoTKeG6HFgwsW7hiQko7er18dgsxF15M316EwYL2W40nKDPWlEhB/iME+sZR5++VQPUO/6w==} + '@codama/fragments@0.1.3': + resolution: {integrity: sha512-ITa3UYs74AWx7se/6EZN+YXqV1Vu89L3n2v0/irsQNbPcy8wP26p/GKns/1H4a8vGvAKuzWdIGO535dwcauInw==} - '@codama/node-types@1.8.0': - resolution: {integrity: sha512-KE9K9jjEdeFKtDKRrjjm5YwEkg7l+YwoWiH2w1UXJL/x6P5uul6+O5WnGsJn8IFtBVSLN4hbD7YSsZ0/lktJxQ==} + '@codama/node-types@1.10.0': + resolution: {integrity: sha512-fMoJQ8TgB9A5Pl+GA8ffhDbGK63cV1nrbDrYTgIhPDKeVqBSdkSAoAuAtvWfRd7X2ML73H6RRi6jZRJmo2xfHQ==} - '@codama/nodes-from-anchor@1.5.1': - resolution: {integrity: sha512-AQee53GV/OY+jJE4PsORPoSagFwGMul1cpSBYhKzvxcrJg1xBesiblPqweNDVIW3NyskHB+x1kQpuq6bsJdb+w==} + '@codama/nodes-from-anchor@1.5.3': + resolution: {integrity: sha512-EcQ2QIty7LK2Vv5vKLprE/qsT13iDBRAvhH79NQauqP+QL6dFD9hCNmu0gfXZK0i7T0vafs55pddExO79qG7Dw==} - '@codama/nodes@1.8.0': - resolution: {integrity: sha512-rl+7BxYatAE6uq5h9b5fEilGLd3YeJaJxqgLDAvdKQ5pspq6ch5ww9NiigkwdvDYli1Yk56PRhMZdLsiZIVgAA==} + '@codama/nodes@1.10.0': + resolution: {integrity: sha512-P3NXVw6kZq2VDb4wYU6zQ3xFksDqSuDYqp11P6Dpei9XIZZ11NtL2jBjBcqsIudGIx104bMgSo4SZ7Hlej056A==} - '@codama/renderers-core@1.3.9': - resolution: {integrity: sha512-DwMxltM+cldAK+rgtE3jVOmrGZuP7fA/ZLu6vgrsQYBp5dkOXCyiGBdpAz0VlhxaN4lB5TzT2Ia+EVv5G4YdGQ==} + '@codama/renderers-core@1.3.11': + resolution: {integrity: sha512-VSGrfmwPVv5cRAiHitgIq+rc9ktIN/Q9czbUORaqhZCmM+M3Ux5cd5r90PVTbeIt8vI0Zlw74OUFwdKqdEfSNw==} - '@codama/visitors-core@1.8.0': - resolution: {integrity: sha512-CLO0icVvzAutjI7imuN1Rib0/GIBc7hhwQ5gcE3aVAVBHUNS4M27BzagqYTpL0cMC9I1tL9c6lhpCBV2kKs6Zg==} + '@codama/visitors-core@1.10.0': + resolution: {integrity: sha512-8koBGs4m/NI4nDLJZx7Q1KzRUKkBy/GAnZq4xOEFGlhTvDCIAzEgo3biAhNFynGY45gzZJSX08QTDWKsPRjnaA==} - '@codama/visitors@1.8.0': - resolution: {integrity: sha512-vQ863YkHBdLWZeaiPKisiRe0bbuBEBe9xDWVcLCCLRsmKCQuS+GB4bKxIwrlTr9cxCOGyQLkI7B/yAqyYEdbmw==} + '@codama/visitors@1.10.0': + resolution: {integrity: sha512-xAGKosZQnXBo9MiHFipA9Dsec1k9nbpv5UgUyY1j3l85FGEZQT2tHqrNxtBmPrC3LNSLVOfAdiUUMVUXUX5ydA==} '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -3728,52 +3728,52 @@ snapshots: human-id: 4.2.0 prettier: 2.8.8 - '@codama/errors@1.8.0': + '@codama/errors@1.10.0': dependencies: - '@codama/node-types': 1.8.0 + '@codama/node-types': 1.10.0 commander: 14.0.3 picocolors: 1.1.1 - '@codama/fragments@0.1.1': + '@codama/fragments@0.1.3': dependencies: - '@codama/errors': 1.8.0 + '@codama/errors': 1.10.0 - '@codama/node-types@1.8.0': {} + '@codama/node-types@1.10.0': {} - '@codama/nodes-from-anchor@1.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': + '@codama/nodes-from-anchor@1.5.3(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3)': dependencies: - '@codama/errors': 1.8.0 - '@codama/nodes': 1.8.0 - '@codama/visitors': 1.8.0 + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/visitors': 1.10.0 '@noble/hashes': 2.0.1 '@solana/codecs': 5.5.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.9.3) transitivePeerDependencies: - fastestsmallesttextencoderdecoder - typescript - '@codama/nodes@1.8.0': + '@codama/nodes@1.10.0': dependencies: - '@codama/errors': 1.8.0 - '@codama/node-types': 1.8.0 + '@codama/errors': 1.10.0 + '@codama/node-types': 1.10.0 - '@codama/renderers-core@1.3.9': + '@codama/renderers-core@1.3.11': dependencies: - '@codama/errors': 1.8.0 - '@codama/fragments': 0.1.1 - '@codama/nodes': 1.8.0 - '@codama/visitors-core': 1.8.0 + '@codama/errors': 1.10.0 + '@codama/fragments': 0.1.3 + '@codama/nodes': 1.10.0 + '@codama/visitors-core': 1.10.0 - '@codama/visitors-core@1.8.0': + '@codama/visitors-core@1.10.0': dependencies: - '@codama/errors': 1.8.0 - '@codama/nodes': 1.8.0 + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 json-stable-stringify: 1.3.0 - '@codama/visitors@1.8.0': + '@codama/visitors@1.10.0': dependencies: - '@codama/errors': 1.8.0 - '@codama/nodes': 1.8.0 - '@codama/visitors-core': 1.8.0 + '@codama/errors': 1.10.0 + '@codama/nodes': 1.10.0 + '@codama/visitors-core': 1.10.0 '@emnapi/core@1.10.0': dependencies: @@ -4665,8 +4665,8 @@ snapshots: '@typescript-eslint/project-service@8.43.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.3) - '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: diff --git a/src/getRenderMapVisitor.ts b/src/getRenderMapVisitor.ts index 55e65a1..9abba5b 100644 --- a/src/getRenderMapVisitor.ts +++ b/src/getRenderMapVisitor.ts @@ -76,7 +76,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { const typeManifest = visit(node, typeManifestVisitor); // Discriminator constants. - const fields = resolveNestedTypeNode(node.data).fields; + const fields = resolveNestedTypeNode(node.data).fields ?? []; const discriminatorConstants = getDiscriminatorConstants({ discriminatorNodes: node.discriminators ?? [], fields, @@ -166,7 +166,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { // Discriminator constants. const discriminatorConstants = getDiscriminatorConstants({ discriminatorNodes: node.discriminators ?? [], - fields: node.arguments, + fields: node.arguments ?? [], getImportFrom, prefix: node.name, typeManifestVisitor, @@ -184,7 +184,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { let hasArgs = false; let hasOptional = false; - node.arguments.forEach(argument => { + (node.arguments ?? []).forEach(argument => { const argumentVisitor = getTypeManifestVisitor({ getImportFrom, getTraitsFromNode, @@ -225,7 +225,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { }); }); - const struct = structTypeNodeFromInstructionArgumentNodes(node.arguments); + const struct = structTypeNodeFromInstructionArgumentNodes(node.arguments ?? []); const structVisitor = getTypeManifestVisitor({ getImportFrom, getTraitsFromNode, @@ -258,18 +258,18 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { visitProgram(node, { self }) { program = node; let renders = mergeRenderMaps([ - ...node.accounts.map(account => visit(account, self)), - ...node.definedTypes.map(type => visit(type, self)), + ...(node.accounts ?? []).map(account => visit(account, self)), + ...(node.definedTypes ?? []).map(type => visit(type, self)), ...getAllInstructionsWithSubs(node, { leavesOnly: !renderParentInstructions, }).map(ix => visit(ix, self)), ]); // Errors. - if (node.errors.length > 0) { + if ((node.errors ?? []).length > 0) { renders = addToRenderMap(renders, `errors/${snakeCase(node.name)}.rs`, { content: render('errorsPage.njk', { - errors: node.errors, + errors: node.errors ?? [], imports: new ImportMap().toString(dependencyMap), program: node, }), @@ -342,8 +342,8 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) { function getConflictsForInstructionAccountsAndArgs(instruction: InstructionNode): string[] { const allNames = [ - ...instruction.accounts.map(account => account.name), - ...instruction.arguments.map(argument => argument.name), + ...(instruction.accounts ?? []).map(account => account.name), + ...(instruction.arguments ?? []).map(argument => argument.name), ]; const duplicates = allNames.filter((e, i, a) => a.indexOf(e) !== i); return [...new Set(duplicates)]; diff --git a/src/getTypeManifestVisitor.ts b/src/getTypeManifestVisitor.ts index 5bddf8e..219dede 100644 --- a/src/getTypeManifestVisitor.ts +++ b/src/getTypeManifestVisitor.ts @@ -246,7 +246,7 @@ export function getTypeManifestVisitor(options: { throw new Error('Enum type must have a parent name.'); } - const variants = enumType.variants.map(variant => visit(variant, self)); + const variants = (enumType.variants ?? []).map(variant => visit(variant, self)); const variantNames = variants.map(variant => variant.type).join('\n'); const mergedManifest = mergeManifests(variants); @@ -454,7 +454,7 @@ export function getTypeManifestVisitor(options: { throw new Error('Struct type must have a parent name.'); } - const fields = structType.fields.map(field => visit(field, self)); + const fields = (structType.fields ?? []).map(field => visit(field, self)); const fieldTypes = fields.map(field => field.type).join('\n'); const mergedManifest = mergeManifests(fields); @@ -484,7 +484,7 @@ export function getTypeManifestVisitor(options: { }, visitTupleType(tupleType, { self }) { - const items = tupleType.items.map(item => visit(item, self)); + const items = (tupleType.items ?? []).map(item => visit(item, self)); const mergedManifest = mergeManifests(items); return { diff --git a/src/renderValueNodeVisitor.ts b/src/renderValueNodeVisitor.ts index f48af80..e3435bc 100644 --- a/src/renderValueNodeVisitor.ts +++ b/src/renderValueNodeVisitor.ts @@ -35,7 +35,7 @@ export function renderValueNodeVisitor( > { return { visitArrayValue(node) { - const list = node.items.map(v => visit(v, this)); + const list = (node.items ?? []).map(v => visit(v, this)); return { imports: new ImportMap().mergeWith(...list.map(c => c.imports)), render: `[${list.map(c => c.render).join(', ')}]`, @@ -84,6 +84,11 @@ export function renderValueNodeVisitor( render: `${enumName}::${variantName} ${fields}`, }; }, + visitInjectedValue() { + // An injected value is resolved by key from a surrounding provider at a later stage, + // so it has no concrete Rust rendering here. + throw new Error('Unsupported injected value node.'); + }, visitMapEntryValue(node) { const mapKey = visit(node.key, this); const mapValue = visit(node.value, this); @@ -93,7 +98,7 @@ export function renderValueNodeVisitor( }; }, visitMapValue(node) { - const map = node.entries.map(entry => visit(entry, this)); + const map = (node.entries ?? []).map(entry => visit(entry, this)); const imports = new ImportMap().add('std::collection::HashMap'); return { imports: imports.mergeWith(...map.map(c => c.imports)), @@ -119,7 +124,7 @@ export function renderValueNodeVisitor( }; }, visitSetValue(node) { - const set = node.items.map(v => visit(v, this)); + const set = (node.items ?? []).map(v => visit(v, this)); const imports = new ImportMap().add('std::collection::HashSet'); return { imports: imports.mergeWith(...set.map(c => c.imports)), @@ -147,14 +152,14 @@ export function renderValueNodeVisitor( }; }, visitStructValue(node) { - const struct = node.fields.map(field => visit(field, this)); + const struct = (node.fields ?? []).map(field => visit(field, this)); return { imports: new ImportMap().mergeWith(...struct.map(c => c.imports)), render: `{ ${struct.map(c => c.render).join(', ')} }`, }; }, visitTupleValue(node) { - const tuple = node.items.map(v => visit(v, this)); + const tuple = (node.items ?? []).map(v => visit(v, this)); return { imports: new ImportMap().mergeWith(...tuple.map(c => c.imports)), render: `(${tuple.map(c => c.render).join(', ')})`,