Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions e2e/anchor/src/generated/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! This code was AUTOGENERATED using the codama library.
//! Please DO NOT EDIT THIS FILE, instead use visitors
//! to add features, then rerun codama to update it.
//!
//! <https://github.com/codama-idl/codama>
//!

pub const EXTRA_ACCOUNT_METAS: [u8; 19] = [
101, 120, 116, 114, 97, 45, 97, 99, 99, 111, 117, 110, 116, 45, 109, 101, 116, 97, 115,
];

pub const GUARD_V1: [u8; 8] = [103, 117, 97, 114, 100, 95, 118, 49];

pub const WEN_TOKEN_GUARD: [u8; 24] = [
119, 101, 110, 95, 116, 111, 107, 101, 110, 95, 116, 114, 97, 110, 115, 102, 101, 114, 95, 103,
117, 97, 114, 100,
];
1 change: 1 addition & 0 deletions e2e/anchor/src/generated/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//!

pub mod accounts;
pub mod constants;
pub mod errors;
pub mod instructions;
pub mod programs;
Expand Down
13 changes: 13 additions & 0 deletions public/templates/constantsPage.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "layout.njk" %}
{% import "macros.njk" as macros %}

{% block main %}

{{ imports }}

{% for constant in constants | sort(false, false, 'name') %}
{{ macros.docblock(constant.docs) }}
pub const {{ constant.name | snakeCase | upper }}: {{ constant.type }} = {{ constant.value }};
{% endfor %}

{% endblock %}
3 changes: 3 additions & 0 deletions public/templates/rootMod.njk
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{% if accountsToExport.length > 0 %}
pub mod accounts;
{% endif %}
{% if constantsToExport.length > 0 %}
pub mod constants;
{% endif %}
{% if programsToExport.length > 0 %}
pub mod errors;
{% endif %}
Expand Down
54 changes: 54 additions & 0 deletions src/getRenderMapVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { logWarn } from '@codama/errors';
import {
getAllAccounts,
getAllConstants,
getAllDefinedTypes,
getAllInstructionsWithSubs,
getAllPrograms,
Expand Down Expand Up @@ -284,18 +285,44 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
visitRoot(node, { self }) {
const programsToExport = getAllPrograms(node);
const accountsToExport = getAllAccounts(node);
const constantsToExport = getAllConstants(node);
const instructionsToExport = getAllInstructionsWithSubs(node, {
leavesOnly: !renderParentInstructions,
});
const definedTypesToExport = getAllDefinedTypes(node);
const hasAnythingToExport =
programsToExport.length > 0 ||
accountsToExport.length > 0 ||
constantsToExport.length > 0 ||
instructionsToExport.length > 0 ||
definedTypesToExport.length > 0;

const constants = constantsToExport.map(constant => {
const isBytes = isNode(constant.type, 'bytesTypeNode');
const isString = isNode(constant.type, 'stringTypeNode');
const fixedByteArray =
isNode(constant.type, 'fixedSizeTypeNode') &&
isNode(constant.type.type, 'bytesTypeNode') &&
isNode(constant.value, 'stringValueNode')
? parseByteArrayConstant(constant.value.string, constant.type.size)
: null;
const typeManifest = isBytes || isString ? null : visit(constant.type, typeManifestVisitor);
const valueManifest = renderValueNode(constant.value, getImportFrom, true);

return {
...constant,
imports: new ImportMap()
.mergeWith(typeManifest?.imports ?? new ImportMap())
.mergeWith(valueManifest.imports),
type: isBytes ? "&'static [u8]" : isString ? "&'static str" : typeManifest!.type,
value: fixedByteArray ?? `${isBytes ? '&' : ''}${valueManifest.render}`,
};
});
const constantsImports = new ImportMap().mergeWith(...constants.map(constant => constant.imports));

const ctx = {
accountsToExport,
constantsToExport,
definedTypesToExport,
hasAnythingToExport,
instructionsToExport,
Expand All @@ -309,6 +336,16 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
accountsToExport.length > 0
? { content: render('accountsMod.njk', ctx), imports: new ImportMap() }
: undefined,
['constants.rs']:
constantsToExport.length > 0
? {
content: render('constantsPage.njk', {
constants,
imports: constantsImports.toString(dependencyMap),
}),
imports: constantsImports,
}
: undefined,
['errors/mod.rs']:
programsToExport.length > 0
? { content: render('errorsMod.njk', ctx), imports: new ImportMap() }
Expand Down Expand Up @@ -340,6 +377,23 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
);
}

function parseByteArrayConstant(value: string, expectedLength: number): string | null {
try {
const bytes: unknown = JSON.parse(value);
if (
!Array.isArray(bytes) ||
bytes.length !== expectedLength ||
!bytes.every(byte => typeof byte === 'number' && Number.isInteger(byte) && byte >= 0 && byte <= 255)
) {
return null;
}

return `[${bytes.join(', ')}]`;
} catch {
return null;
}
}

function getConflictsForInstructionAccountsAndArgs(instruction: InstructionNode): string[] {
const allNames = [
...(instruction.accounts ?? []).map(account => account.name),
Expand Down
55 changes: 55 additions & 0 deletions test/constantsPage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
bytesTypeNode,
bytesValueNode,
constantNode,
fixedSizeTypeNode,
numberTypeNode,
numberValueNode,
programNode,
rootNode,
stringValueNode,
} from '@codama/nodes';
import { getFromRenderMap } from '@codama/renderers-core';
import { visit } from '@codama/visitors-core';
import { test } from 'vitest';

import { getRenderMapVisitor } from '../src';
import { codeContains } from './_setup';

test('it renders program constants in a top-level constants page', () => {
const node = rootNode(
programNode({
constants: [
constantNode('maxOption', numberTypeNode('u8'), numberValueNode(10), ['Maximum options.']),
constantNode('seed', bytesTypeNode(), bytesValueNode('utf8', 'seed')),
],
name: 'governance',
publicKey: 'GovER5Lthms3bLBqWub97yVrQm9WLZ7YgRrxYQYy2f',
}),
);

const renderMap = visit(node, getRenderMapVisitor());

codeContains(getFromRenderMap(renderMap, 'constants.rs').content, [
'/// Maximum options.',
'pub const MAX_OPTION: u8 = 10;',
"pub const SEED: &'static [u8] = &[115, 101, 101, 100];",
]);
codeContains(getFromRenderMap(renderMap, 'mod.rs').content, 'pub mod constants;');
});

test('it renders JSON byte arrays from Anchor constants as fixed byte arrays', () => {
const node = rootNode(
programNode({
constants: [
constantNode('guardV1', fixedSizeTypeNode(bytesTypeNode(), 3), stringValueNode('[103, 117, 97]')),
],
name: 'tokenGuard',
publicKey: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
}),
);

const renderMap = visit(node, getRenderMapVisitor());

codeContains(getFromRenderMap(renderMap, 'constants.rs').content, 'pub const GUARD_V1: [u8; 3] = [103, 117, 97];');
});