codama-renderers-dart 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +61 -0
- package/readme.md +297 -0
- package/src/fragments/accountPage.ts +201 -0
- package/src/fragments/discriminatorConstants.ts +74 -0
- package/src/fragments/errorPage.ts +82 -0
- package/src/fragments/index.ts +8 -0
- package/src/fragments/indexPage.ts +44 -0
- package/src/fragments/instructionPage.ts +306 -0
- package/src/fragments/pdaPage.ts +128 -0
- package/src/fragments/programPage.ts +63 -0
- package/src/fragments/typePage.ts +554 -0
- package/src/index.ts +13 -0
- package/src/types/global.d.ts +2 -0
- package/src/utils/codecs.ts +19 -0
- package/src/utils/formatCode.ts +35 -0
- package/src/utils/fragment.ts +83 -0
- package/src/utils/importMap.ts +129 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/nameTransformers.ts +112 -0
- package/src/utils/options.ts +46 -0
- package/src/utils/typeManifest.ts +31 -0
- package/src/visitors/getRenderMapVisitor.ts +272 -0
- package/src/visitors/getTypeManifestVisitor.ts +794 -0
- package/src/visitors/index.ts +3 -0
- package/src/visitors/renderVisitor.ts +154 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { BaseFragment } from "@codama/renderers-core";
|
|
2
|
+
|
|
3
|
+
import { DartImportMap } from "./importMap.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A Fragment represents a piece of generated Dart code
|
|
7
|
+
* along with the imports it requires.
|
|
8
|
+
*/
|
|
9
|
+
export type Fragment = BaseFragment &
|
|
10
|
+
Readonly<{
|
|
11
|
+
imports: DartImportMap;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a Fragment from a tagged template literal.
|
|
16
|
+
* Embedded Fragment values have their imports merged automatically.
|
|
17
|
+
*/
|
|
18
|
+
export function fragment(
|
|
19
|
+
strings: TemplateStringsArray,
|
|
20
|
+
...values: (Fragment | string | number | boolean | undefined | null)[]
|
|
21
|
+
): Fragment {
|
|
22
|
+
const imports = new DartImportMap();
|
|
23
|
+
const parts: string[] = [];
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < strings.length; i++) {
|
|
26
|
+
parts.push(strings[i]);
|
|
27
|
+
if (i < values.length) {
|
|
28
|
+
const value = values[i];
|
|
29
|
+
if (value != null && typeof value === "object" && "imports" in value) {
|
|
30
|
+
parts.push(value.content);
|
|
31
|
+
imports.mergeWith(value.imports);
|
|
32
|
+
} else {
|
|
33
|
+
parts.push(String(value ?? ""));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
content: parts.join(""),
|
|
40
|
+
imports,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create an empty fragment.
|
|
46
|
+
*/
|
|
47
|
+
export function emptyFragment(): Fragment {
|
|
48
|
+
return { content: "", imports: new DartImportMap() };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a fragment from a raw string.
|
|
53
|
+
*/
|
|
54
|
+
export function fragmentFromString(content: string): Fragment {
|
|
55
|
+
return { content, imports: new DartImportMap() };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Merge multiple fragments with a joiner function.
|
|
60
|
+
*/
|
|
61
|
+
export function mergeFragments(
|
|
62
|
+
fragments: Fragment[],
|
|
63
|
+
joiner: (contents: string[]) => string,
|
|
64
|
+
): Fragment {
|
|
65
|
+
const imports = new DartImportMap();
|
|
66
|
+
const contents: string[] = [];
|
|
67
|
+
|
|
68
|
+
for (const frag of fragments) {
|
|
69
|
+
contents.push(frag.content);
|
|
70
|
+
imports.mergeWith(frag.imports);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { content: joiner(contents), imports };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Helper to add a Dart import to a fragment and return a type name fragment.
|
|
78
|
+
*/
|
|
79
|
+
export function use(typeName: string, module: string): Fragment {
|
|
80
|
+
const imports = new DartImportMap();
|
|
81
|
+
imports.add(module);
|
|
82
|
+
return { content: typeName, imports };
|
|
83
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps logical module names to Dart package: URIs.
|
|
3
|
+
*/
|
|
4
|
+
export const DART_EXTERNAL_PACKAGE_MAP: Record<string, string> = {
|
|
5
|
+
// Dart core
|
|
6
|
+
dartTypedData: "dart:typed_data",
|
|
7
|
+
dartConvert: "dart:convert",
|
|
8
|
+
meta: "package:meta/meta.dart",
|
|
9
|
+
|
|
10
|
+
// solana_kit packages
|
|
11
|
+
solanaAddresses: "package:solana_kit_addresses/solana_kit_addresses.dart",
|
|
12
|
+
solanaCodecsCore: "package:solana_kit_codecs_core/solana_kit_codecs_core.dart",
|
|
13
|
+
solanaCodecsDataStructures:
|
|
14
|
+
"package:solana_kit_codecs_data_structures/solana_kit_codecs_data_structures.dart",
|
|
15
|
+
solanaCodecsNumbers:
|
|
16
|
+
"package:solana_kit_codecs_numbers/solana_kit_codecs_numbers.dart",
|
|
17
|
+
solanaCodecsStrings:
|
|
18
|
+
"package:solana_kit_codecs_strings/solana_kit_codecs_strings.dart",
|
|
19
|
+
solanaErrors: "package:solana_kit_errors/solana_kit_errors.dart",
|
|
20
|
+
solanaAccounts: "package:solana_kit_accounts/solana_kit_accounts.dart",
|
|
21
|
+
solanaInstructions:
|
|
22
|
+
"package:solana_kit_instructions/solana_kit_instructions.dart",
|
|
23
|
+
solanaPrograms: "package:solana_kit_programs/solana_kit_programs.dart",
|
|
24
|
+
solanaRpcTypes: "package:solana_kit_rpc_types/solana_kit_rpc_types.dart",
|
|
25
|
+
solanaSigners: "package:solana_kit_signers/solana_kit_signers.dart",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tracks Dart import URIs needed by generated code.
|
|
30
|
+
*
|
|
31
|
+
* Dart imports entire libraries (not individual symbols), so we only
|
|
32
|
+
* need to track which package URIs are used, not specific symbols.
|
|
33
|
+
*/
|
|
34
|
+
export class DartImportMap {
|
|
35
|
+
private _imports = new Set<string>();
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Add a logical module name (resolved via DART_EXTERNAL_PACKAGE_MAP)
|
|
39
|
+
* or a raw Dart import URI.
|
|
40
|
+
*/
|
|
41
|
+
add(module: string): this {
|
|
42
|
+
this._imports.add(module);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Merge another DartImportMap into this one.
|
|
48
|
+
*/
|
|
49
|
+
mergeWith(other: DartImportMap): this {
|
|
50
|
+
for (const imp of other._imports) {
|
|
51
|
+
this._imports.add(imp);
|
|
52
|
+
}
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if this import map is empty.
|
|
58
|
+
*/
|
|
59
|
+
get isEmpty(): boolean {
|
|
60
|
+
return this._imports.size === 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all raw module keys.
|
|
65
|
+
*/
|
|
66
|
+
get modules(): Set<string> {
|
|
67
|
+
return new Set(this._imports);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve all imports to Dart import URIs and return them sorted.
|
|
72
|
+
* @param internalMap Maps logical names to relative file paths for generated code cross-references.
|
|
73
|
+
*/
|
|
74
|
+
resolve(internalMap: Record<string, string> = {}): string[] {
|
|
75
|
+
const uris = new Set<string>();
|
|
76
|
+
|
|
77
|
+
for (const module of this._imports) {
|
|
78
|
+
// Check internal map first (generated cross-references)
|
|
79
|
+
if (module in internalMap) {
|
|
80
|
+
uris.add(internalMap[module]);
|
|
81
|
+
}
|
|
82
|
+
// Then external package map
|
|
83
|
+
else if (module in DART_EXTERNAL_PACKAGE_MAP) {
|
|
84
|
+
uris.add(DART_EXTERNAL_PACKAGE_MAP[module]);
|
|
85
|
+
}
|
|
86
|
+
// Assume it's already a raw URI (e.g., "package:..." or relative path)
|
|
87
|
+
else {
|
|
88
|
+
uris.add(module);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Sort: dart: first, then package:, then relative
|
|
93
|
+
return [...uris].sort((a, b) => {
|
|
94
|
+
const aWeight = a.startsWith("dart:") ? 0 : a.startsWith("package:") ? 1 : 2;
|
|
95
|
+
const bWeight = b.startsWith("dart:") ? 0 : b.startsWith("package:") ? 1 : 2;
|
|
96
|
+
if (aWeight !== bWeight) return aWeight - bWeight;
|
|
97
|
+
return a.localeCompare(b);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Render all imports as Dart import statements.
|
|
103
|
+
*/
|
|
104
|
+
toString(internalMap: Record<string, string> = {}): string {
|
|
105
|
+
const resolved = this.resolve(internalMap);
|
|
106
|
+
if (resolved.length === 0) return "";
|
|
107
|
+
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
let lastPrefix = "";
|
|
110
|
+
|
|
111
|
+
for (const uri of resolved) {
|
|
112
|
+
const prefix = uri.startsWith("dart:")
|
|
113
|
+
? "dart"
|
|
114
|
+
: uri.startsWith("package:")
|
|
115
|
+
? "package"
|
|
116
|
+
: "relative";
|
|
117
|
+
|
|
118
|
+
// Add blank line between groups
|
|
119
|
+
if (lastPrefix && lastPrefix !== prefix) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
}
|
|
122
|
+
lastPrefix = prefix;
|
|
123
|
+
|
|
124
|
+
lines.push(`import '${uri}';`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { CamelCaseString } from "@codama/nodes";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Naming convention transformers for Dart code generation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Convert a string to PascalCase */
|
|
8
|
+
export function pascalCase(str: string): string {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
|
|
11
|
+
.replace(/^(.)/, (c) => c.toUpperCase());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Convert a string to camelCase */
|
|
15
|
+
export function camelCase(str: string): string {
|
|
16
|
+
const pascal = pascalCase(str);
|
|
17
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Convert a string to snake_case */
|
|
21
|
+
export function snakeCase(str: string): string {
|
|
22
|
+
return str
|
|
23
|
+
.replace(/([A-Z])/g, "_$1")
|
|
24
|
+
.replace(/[-\s]+/g, "_")
|
|
25
|
+
.replace(/^_/, "")
|
|
26
|
+
.replace(/_+/g, "_")
|
|
27
|
+
.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Convert a string to SCREAMING_SNAKE_CASE */
|
|
31
|
+
export function screamingSnakeCase(str: string): string {
|
|
32
|
+
return snakeCase(str).toUpperCase();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* All name transformation functions for Dart code generation.
|
|
37
|
+
*/
|
|
38
|
+
export interface DartNameApi {
|
|
39
|
+
/** Data class name: `PascalCase` */
|
|
40
|
+
dataType(name: string): string;
|
|
41
|
+
/** Encoder function name: `get{PascalCase}Encoder` */
|
|
42
|
+
encoderFunction(name: string): string;
|
|
43
|
+
/** Decoder function name: `get{PascalCase}Decoder` */
|
|
44
|
+
decoderFunction(name: string): string;
|
|
45
|
+
/** Codec function name: `get{PascalCase}Codec` */
|
|
46
|
+
codecFunction(name: string): string;
|
|
47
|
+
/** Account fetch function: `fetch{PascalCase}` */
|
|
48
|
+
accountFetchFunction(name: string): string;
|
|
49
|
+
/** Account fetch maybe function: `fetchMaybe{PascalCase}` */
|
|
50
|
+
accountFetchMaybeFunction(name: string): string;
|
|
51
|
+
/** Account decode function: `decode{PascalCase}` */
|
|
52
|
+
accountDecodeFunction(name: string): string;
|
|
53
|
+
/** Account size constant: `{camelCase}Size` */
|
|
54
|
+
accountSizeConstant(name: string): string;
|
|
55
|
+
/** PDA finder function: `find{PascalCase}Pda` */
|
|
56
|
+
pdaFindFunction(name: string): string;
|
|
57
|
+
/** Instruction builder function: `get{PascalCase}Instruction` */
|
|
58
|
+
instructionFunction(name: string): string;
|
|
59
|
+
/** Instruction parse function: `parse{PascalCase}Instruction` */
|
|
60
|
+
instructionParseFunction(name: string): string;
|
|
61
|
+
/** Program address constant: `{camelCase}ProgramAddress` */
|
|
62
|
+
programAddressConstant(name: string): string;
|
|
63
|
+
/** Error code constant: `{programName}Error{PascalCase}` */
|
|
64
|
+
errorConstant(programName: string, errorName: string): string;
|
|
65
|
+
/** Error message function: `get{PascalCase}ErrorMessage` */
|
|
66
|
+
errorMessageFunction(name: string): string;
|
|
67
|
+
/** Enum variant: `camelCase` */
|
|
68
|
+
enumVariant(name: string): string;
|
|
69
|
+
/** Sealed class variant (data enum): `PascalCase` */
|
|
70
|
+
sealedClassVariant(parentName: string, variantName: string): string;
|
|
71
|
+
/** File name: `snake_case.dart` */
|
|
72
|
+
fileName(name: string): string;
|
|
73
|
+
/** Discriminator constant: `{camelCase}Discriminator` */
|
|
74
|
+
discriminatorConstant(name: string): string;
|
|
75
|
+
/** isX() helper for type check: `is{PascalCase}` */
|
|
76
|
+
isTypeFunction(name: string): string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Create the default Dart name API.
|
|
81
|
+
*/
|
|
82
|
+
export function createDartNameApi(): DartNameApi {
|
|
83
|
+
return {
|
|
84
|
+
dataType: (name) => pascalCase(name),
|
|
85
|
+
encoderFunction: (name) => `get${pascalCase(name)}Encoder`,
|
|
86
|
+
decoderFunction: (name) => `get${pascalCase(name)}Decoder`,
|
|
87
|
+
codecFunction: (name) => `get${pascalCase(name)}Codec`,
|
|
88
|
+
accountFetchFunction: (name) => `fetch${pascalCase(name)}`,
|
|
89
|
+
accountFetchMaybeFunction: (name) => `fetchMaybe${pascalCase(name)}`,
|
|
90
|
+
accountDecodeFunction: (name) => `decode${pascalCase(name)}`,
|
|
91
|
+
accountSizeConstant: (name) => `${camelCase(name)}Size`,
|
|
92
|
+
pdaFindFunction: (name) => `find${pascalCase(name)}Pda`,
|
|
93
|
+
instructionFunction: (name) => `get${pascalCase(name)}Instruction`,
|
|
94
|
+
instructionParseFunction: (name) => `parse${pascalCase(name)}Instruction`,
|
|
95
|
+
programAddressConstant: (name) => `${camelCase(name)}ProgramAddress`,
|
|
96
|
+
errorConstant: (programName, errorName) =>
|
|
97
|
+
`${camelCase(programName)}Error${pascalCase(errorName)}`,
|
|
98
|
+
errorMessageFunction: (name) => `get${pascalCase(name)}ErrorMessage`,
|
|
99
|
+
enumVariant: (name) => camelCase(name),
|
|
100
|
+
sealedClassVariant: (_parentName, variantName) => pascalCase(variantName),
|
|
101
|
+
isTypeFunction: (name) => `is${pascalCase(name)}`,
|
|
102
|
+
fileName: (name) => snakeCase(name),
|
|
103
|
+
discriminatorConstant: (name) => `${camelCase(name)}Discriminator`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Convert a CamelCaseString to a regular string.
|
|
109
|
+
*/
|
|
110
|
+
export function fromCamelCaseString(str: CamelCaseString): string {
|
|
111
|
+
return str as string;
|
|
112
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LinkableDictionary,
|
|
3
|
+
} from "@codama/visitors-core";
|
|
4
|
+
|
|
5
|
+
import type { DartNameApi } from "./nameTransformers.js";
|
|
6
|
+
import type { TypeManifestVisitor } from "../visitors/getTypeManifestVisitor.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options for the top-level renderVisitor.
|
|
10
|
+
*/
|
|
11
|
+
export interface RenderOptions extends GetRenderMapOptions {
|
|
12
|
+
/** Whether to delete the output folder before rendering. Default: true. */
|
|
13
|
+
deleteFolderBeforeRendering?: boolean;
|
|
14
|
+
/** Whether to run `dart format` on generated files. Default: false. */
|
|
15
|
+
formatCode?: boolean;
|
|
16
|
+
/** The Dart package name for pubspec.yaml generation. */
|
|
17
|
+
dartPackageName?: string;
|
|
18
|
+
/** Custom dependency overrides for the generated pubspec.yaml. */
|
|
19
|
+
dartDependencies?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for the getRenderMapVisitor.
|
|
24
|
+
*/
|
|
25
|
+
export interface GetRenderMapOptions {
|
|
26
|
+
/** Custom name API overrides. */
|
|
27
|
+
nameApi?: Partial<DartNameApi>;
|
|
28
|
+
/** Dependency map overrides (logical module -> Dart package URI). */
|
|
29
|
+
dependencyMap?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Shared rendering context passed to fragment generators.
|
|
34
|
+
*/
|
|
35
|
+
export interface RenderScope {
|
|
36
|
+
/** The resolved name API for Dart naming conventions. */
|
|
37
|
+
nameApi: DartNameApi;
|
|
38
|
+
/** The type manifest visitor for resolving type nodes. */
|
|
39
|
+
typeManifestVisitor: TypeManifestVisitor;
|
|
40
|
+
/** Linkable nodes dictionary for cross-references. */
|
|
41
|
+
linkables: LinkableDictionary;
|
|
42
|
+
/** Custom dependency map. */
|
|
43
|
+
dependencyMap: Record<string, string>;
|
|
44
|
+
/** Internal import map (logical name -> relative file path). */
|
|
45
|
+
internalImportMap: Record<string, string>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Fragment } from "./fragment.js";
|
|
2
|
+
import { emptyFragment } from "./fragment.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Represents how a Codama type maps to Dart types and codec expressions.
|
|
6
|
+
*/
|
|
7
|
+
export interface TypeManifest {
|
|
8
|
+
/** The Dart type name (e.g. `int`, `BigInt`, `Address`, `MyStruct`). */
|
|
9
|
+
type: Fragment;
|
|
10
|
+
/** The Dart encoder expression (e.g. `getU8Encoder()`). */
|
|
11
|
+
encoder: Fragment;
|
|
12
|
+
/** The Dart decoder expression (e.g. `getU8Decoder()`). */
|
|
13
|
+
decoder: Fragment;
|
|
14
|
+
/** The Dart value expression for default values. */
|
|
15
|
+
value: Fragment;
|
|
16
|
+
/** Whether this is a scalar enum (Dart `enum`). */
|
|
17
|
+
isEnum: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create an empty TypeManifest.
|
|
22
|
+
*/
|
|
23
|
+
export function emptyTypeManifest(): TypeManifest {
|
|
24
|
+
return {
|
|
25
|
+
type: emptyFragment(),
|
|
26
|
+
encoder: emptyFragment(),
|
|
27
|
+
decoder: emptyFragment(),
|
|
28
|
+
value: emptyFragment(),
|
|
29
|
+
isEnum: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AccountNode,
|
|
3
|
+
type DefinedTypeNode,
|
|
4
|
+
type InstructionNode,
|
|
5
|
+
type PdaNode,
|
|
6
|
+
type ProgramNode,
|
|
7
|
+
type RootNode,
|
|
8
|
+
} from "@codama/nodes";
|
|
9
|
+
import {
|
|
10
|
+
type RenderMap,
|
|
11
|
+
addToRenderMap,
|
|
12
|
+
createRenderMap,
|
|
13
|
+
mergeRenderMaps,
|
|
14
|
+
} from "@codama/renderers-core";
|
|
15
|
+
import {
|
|
16
|
+
type Visitor,
|
|
17
|
+
LinkableDictionary,
|
|
18
|
+
NodeStack,
|
|
19
|
+
extendVisitor,
|
|
20
|
+
pipe,
|
|
21
|
+
recordLinkablesOnFirstVisitVisitor,
|
|
22
|
+
recordNodeStackVisitor,
|
|
23
|
+
staticVisitor,
|
|
24
|
+
visit,
|
|
25
|
+
} from "@codama/visitors-core";
|
|
26
|
+
|
|
27
|
+
import type { Fragment } from "../utils/fragment.js";
|
|
28
|
+
import type { GetRenderMapOptions, RenderScope } from "../utils/options.js";
|
|
29
|
+
import { createDartNameApi } from "../utils/nameTransformers.js";
|
|
30
|
+
import { snakeCase } from "../utils/nameTransformers.js";
|
|
31
|
+
import { getTypeManifestVisitor } from "./getTypeManifestVisitor.js";
|
|
32
|
+
|
|
33
|
+
import { getAccountPageFragment } from "../fragments/accountPage.js";
|
|
34
|
+
import { getErrorPageFragment } from "../fragments/errorPage.js";
|
|
35
|
+
import { getIndexPageFragment, getRootIndexPageFragment } from "../fragments/indexPage.js";
|
|
36
|
+
import { getInstructionPageFragment } from "../fragments/instructionPage.js";
|
|
37
|
+
import { getPdaPageFragment } from "../fragments/pdaPage.js";
|
|
38
|
+
import { getProgramPageFragment } from "../fragments/programPage.js";
|
|
39
|
+
import { getTypePageFragment } from "../fragments/typePage.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a visitor that maps Codama nodes to a RenderMap of Dart files.
|
|
43
|
+
*/
|
|
44
|
+
export function getRenderMapVisitor(
|
|
45
|
+
options: GetRenderMapOptions = {},
|
|
46
|
+
): Visitor<RenderMap<Fragment>, "rootNode" | "programNode" | "accountNode" | "definedTypeNode" | "instructionNode" | "pdaNode"> {
|
|
47
|
+
const nameApi = {
|
|
48
|
+
...createDartNameApi(),
|
|
49
|
+
...options.nameApi,
|
|
50
|
+
};
|
|
51
|
+
const dependencyMap = options.dependencyMap ?? {};
|
|
52
|
+
|
|
53
|
+
const stack = new NodeStack();
|
|
54
|
+
const linkables = new LinkableDictionary();
|
|
55
|
+
|
|
56
|
+
const typeManifestVisitor = getTypeManifestVisitor({
|
|
57
|
+
nameApi,
|
|
58
|
+
linkables,
|
|
59
|
+
stack,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const scope: RenderScope = {
|
|
63
|
+
nameApi,
|
|
64
|
+
typeManifestVisitor,
|
|
65
|
+
linkables,
|
|
66
|
+
dependencyMap,
|
|
67
|
+
internalImportMap: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return pipe(
|
|
71
|
+
staticVisitor(
|
|
72
|
+
() => createRenderMap<Fragment>(),
|
|
73
|
+
{
|
|
74
|
+
keys: [
|
|
75
|
+
"rootNode",
|
|
76
|
+
"programNode",
|
|
77
|
+
"accountNode",
|
|
78
|
+
"definedTypeNode",
|
|
79
|
+
"instructionNode",
|
|
80
|
+
"pdaNode",
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
),
|
|
84
|
+
(v) =>
|
|
85
|
+
extendVisitor(v, {
|
|
86
|
+
visitRoot(node: RootNode, { self }) {
|
|
87
|
+
const programMaps = [
|
|
88
|
+
visit(node.program, self),
|
|
89
|
+
...node.additionalPrograms.map((p) => visit(p, self)),
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
return mergeRenderMaps(programMaps);
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
visitProgram(node: ProgramNode, { self }) {
|
|
96
|
+
const programName = node.name as string;
|
|
97
|
+
const maps: RenderMap<Fragment>[] = [];
|
|
98
|
+
|
|
99
|
+
// Track file names for barrel exports
|
|
100
|
+
const accountFiles: string[] = [];
|
|
101
|
+
const instructionFiles: string[] = [];
|
|
102
|
+
const typeFiles: string[] = [];
|
|
103
|
+
const pdaFiles: string[] = [];
|
|
104
|
+
const errorFiles: string[] = [];
|
|
105
|
+
const programFiles: string[] = [];
|
|
106
|
+
|
|
107
|
+
// Visit accounts
|
|
108
|
+
for (const account of node.accounts ?? []) {
|
|
109
|
+
maps.push(visit(account, self));
|
|
110
|
+
accountFiles.push(`${snakeCase(account.name as string)}.dart`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Visit instructions
|
|
114
|
+
for (const instruction of node.instructions ?? []) {
|
|
115
|
+
maps.push(visit(instruction, self));
|
|
116
|
+
instructionFiles.push(
|
|
117
|
+
`${snakeCase(instruction.name as string)}.dart`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Visit defined types
|
|
122
|
+
for (const definedType of node.definedTypes ?? []) {
|
|
123
|
+
maps.push(visit(definedType, self));
|
|
124
|
+
typeFiles.push(
|
|
125
|
+
`${snakeCase(definedType.name as string)}.dart`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Visit PDAs
|
|
130
|
+
for (const pda of node.pdas ?? []) {
|
|
131
|
+
maps.push(visit(pda, self));
|
|
132
|
+
pdaFiles.push(`${snakeCase(pda.name as string)}.dart`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Program page
|
|
136
|
+
const programFragment = getProgramPageFragment(node, scope);
|
|
137
|
+
const programFileName = `${snakeCase(programName)}.dart`;
|
|
138
|
+
programFiles.push(programFileName);
|
|
139
|
+
let programMap = createRenderMap<Fragment>();
|
|
140
|
+
programMap = addToRenderMap(programMap, `programs/${programFileName}`, programFragment);
|
|
141
|
+
maps.push(programMap);
|
|
142
|
+
|
|
143
|
+
// Error page
|
|
144
|
+
const errors = node.errors ?? [];
|
|
145
|
+
if (errors.length > 0) {
|
|
146
|
+
const errorFragment = getErrorPageFragment(node, scope);
|
|
147
|
+
const errorFileName = `${snakeCase(programName)}.dart`;
|
|
148
|
+
errorFiles.push(errorFileName);
|
|
149
|
+
let errorMap = createRenderMap<Fragment>();
|
|
150
|
+
errorMap = addToRenderMap(errorMap, `errors/${errorFileName}`, errorFragment);
|
|
151
|
+
maps.push(errorMap);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Barrel exports
|
|
155
|
+
const categories: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (accountFiles.length > 0) {
|
|
158
|
+
categories.push("accounts");
|
|
159
|
+
let indexMap = createRenderMap<Fragment>();
|
|
160
|
+
indexMap = addToRenderMap(
|
|
161
|
+
indexMap,
|
|
162
|
+
"accounts/accounts.dart",
|
|
163
|
+
getIndexPageFragment(accountFiles),
|
|
164
|
+
);
|
|
165
|
+
maps.push(indexMap);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (instructionFiles.length > 0) {
|
|
169
|
+
categories.push("instructions");
|
|
170
|
+
let indexMap = createRenderMap<Fragment>();
|
|
171
|
+
indexMap = addToRenderMap(
|
|
172
|
+
indexMap,
|
|
173
|
+
"instructions/instructions.dart",
|
|
174
|
+
getIndexPageFragment(instructionFiles),
|
|
175
|
+
);
|
|
176
|
+
maps.push(indexMap);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeFiles.length > 0) {
|
|
180
|
+
categories.push("types");
|
|
181
|
+
let indexMap = createRenderMap<Fragment>();
|
|
182
|
+
indexMap = addToRenderMap(
|
|
183
|
+
indexMap,
|
|
184
|
+
"types/types.dart",
|
|
185
|
+
getIndexPageFragment(typeFiles),
|
|
186
|
+
);
|
|
187
|
+
maps.push(indexMap);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (pdaFiles.length > 0) {
|
|
191
|
+
categories.push("pdas");
|
|
192
|
+
let indexMap = createRenderMap<Fragment>();
|
|
193
|
+
indexMap = addToRenderMap(
|
|
194
|
+
indexMap,
|
|
195
|
+
"pdas/pdas.dart",
|
|
196
|
+
getIndexPageFragment(pdaFiles),
|
|
197
|
+
);
|
|
198
|
+
maps.push(indexMap);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (errorFiles.length > 0) {
|
|
202
|
+
categories.push("errors");
|
|
203
|
+
let indexMap = createRenderMap<Fragment>();
|
|
204
|
+
indexMap = addToRenderMap(
|
|
205
|
+
indexMap,
|
|
206
|
+
"errors/errors.dart",
|
|
207
|
+
getIndexPageFragment(errorFiles),
|
|
208
|
+
);
|
|
209
|
+
maps.push(indexMap);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (programFiles.length > 0) {
|
|
213
|
+
categories.push("programs");
|
|
214
|
+
let indexMap = createRenderMap<Fragment>();
|
|
215
|
+
indexMap = addToRenderMap(
|
|
216
|
+
indexMap,
|
|
217
|
+
"programs/programs.dart",
|
|
218
|
+
getIndexPageFragment(programFiles),
|
|
219
|
+
);
|
|
220
|
+
maps.push(indexMap);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Root barrel
|
|
224
|
+
if (categories.length > 0) {
|
|
225
|
+
let rootMap = createRenderMap<Fragment>();
|
|
226
|
+
rootMap = addToRenderMap(
|
|
227
|
+
rootMap,
|
|
228
|
+
`${snakeCase(programName)}.dart`,
|
|
229
|
+
getRootIndexPageFragment(categories),
|
|
230
|
+
);
|
|
231
|
+
maps.push(rootMap);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return mergeRenderMaps(maps);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
visitAccount(node: AccountNode, { self }) {
|
|
238
|
+
const fileName = `${snakeCase(node.name as string)}.dart`;
|
|
239
|
+
const frag = getAccountPageFragment(node, scope);
|
|
240
|
+
let map = createRenderMap<Fragment>();
|
|
241
|
+
map = addToRenderMap(map, `accounts/${fileName}`, frag);
|
|
242
|
+
return map;
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
visitDefinedType(node: DefinedTypeNode, { self }) {
|
|
246
|
+
const fileName = `${snakeCase(node.name as string)}.dart`;
|
|
247
|
+
const frag = getTypePageFragment(node, scope);
|
|
248
|
+
let map = createRenderMap<Fragment>();
|
|
249
|
+
map = addToRenderMap(map, `types/${fileName}`, frag);
|
|
250
|
+
return map;
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
visitInstruction(node: InstructionNode, { self }) {
|
|
254
|
+
const fileName = `${snakeCase(node.name as string)}.dart`;
|
|
255
|
+
const frag = getInstructionPageFragment(node, scope);
|
|
256
|
+
let map = createRenderMap<Fragment>();
|
|
257
|
+
map = addToRenderMap(map, `instructions/${fileName}`, frag);
|
|
258
|
+
return map;
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
visitPda(node: PdaNode, { self }) {
|
|
262
|
+
const fileName = `${snakeCase(node.name as string)}.dart`;
|
|
263
|
+
const frag = getPdaPageFragment(node, scope);
|
|
264
|
+
let map = createRenderMap<Fragment>();
|
|
265
|
+
map = addToRenderMap(map, `pdas/${fileName}`, frag);
|
|
266
|
+
return map;
|
|
267
|
+
},
|
|
268
|
+
}),
|
|
269
|
+
(v) => recordNodeStackVisitor(v, stack),
|
|
270
|
+
(v) => recordLinkablesOnFirstVisitVisitor(v, linkables),
|
|
271
|
+
);
|
|
272
|
+
}
|