Skip to content

Commit 44f725e

Browse files
authored
Refactor Fragments and RenderMaps in renderers-core (#788)
1 parent d6e2349 commit 44f725e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1800
-1333
lines changed

.changeset/many-eyes-rule.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@codama/renderers-core': minor
3+
'@codama/renderers-vixen-parser': patch
4+
'@codama/renderers-js-umi': patch
5+
'@codama/renderers-rust': patch
6+
'@codama/renderers-js': patch
7+
---
8+
9+
Refactor fragments and render maps so that they are functional and immutable.

packages/renderers-core/src/RenderMap.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export type BaseFragment = Readonly<{ content: string }>;
2+
3+
export function mapFragmentContent<TFragment extends BaseFragment>(
4+
fragment: TFragment,
5+
mapContent: (content: string) => string,
6+
): TFragment {
7+
return setFragmentContent(fragment, mapContent(fragment.content));
8+
}
9+
10+
export function setFragmentContent<TFragment extends BaseFragment>(fragment: TFragment, content: string): TFragment {
11+
return Object.freeze({ ...fragment, content });
12+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1+
export * from './fragment';
12
export * from './fs';
23
export * from './path';
34
export * from './renderMap';
4-
export * from './writeRenderMapVisitor';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND, CodamaError } from '@codama/errors';
2+
import { NodeKind } from '@codama/nodes';
3+
import { mapVisitor, Visitor } from '@codama/visitors-core';
4+
5+
import { BaseFragment } from './fragment';
6+
import { writeFile } from './fs';
7+
import { Path } from './path';
8+
9+
export type RenderMap = ReadonlyMap<Path, string>;
10+
11+
export function renderMap(): RenderMap {
12+
return new Map<Path, string>();
13+
}
14+
15+
export function addToRenderMap(renderMap: RenderMap, path: Path, content: BaseFragment | string): RenderMap {
16+
const newMap = new Map(renderMap);
17+
newMap.set(path, typeof content === 'string' ? content : content.content);
18+
return newMap;
19+
}
20+
21+
export function removeFromRenderMap(renderMap: RenderMap, path: Path): RenderMap {
22+
const newMap = new Map(renderMap);
23+
newMap.delete(path);
24+
return newMap;
25+
}
26+
27+
export function mergeRenderMaps(renderMaps: RenderMap[]): RenderMap {
28+
const merged = new Map(renderMaps[0]);
29+
for (const map of renderMaps.slice(1)) {
30+
for (const [key, value] of map) {
31+
merged.set(key, value);
32+
}
33+
}
34+
return merged;
35+
}
36+
37+
export function mapRenderMapContent(renderMap: RenderMap, fn: (content: string) => string): RenderMap {
38+
const newMap = new Map<Path, string>();
39+
for (const [key, value] of renderMap) {
40+
newMap.set(key, fn(value));
41+
}
42+
return newMap;
43+
}
44+
45+
export async function mapRenderMapContentAsync(
46+
renderMap: RenderMap,
47+
fn: (content: string) => Promise<string>,
48+
): Promise<RenderMap> {
49+
const entries = await Promise.all([
50+
...[...renderMap.entries()].map(async ([key, value]) => [key, await fn(value)] as const),
51+
]);
52+
return new Map<Path, string>(entries);
53+
}
54+
55+
export function getFromRenderMap(renderMap: RenderMap, path: Path): string {
56+
const value = renderMap.get(path);
57+
if (value === undefined) {
58+
throw new CodamaError(CODAMA_ERROR__VISITORS__RENDER_MAP_KEY_NOT_FOUND, { key: path });
59+
}
60+
return value;
61+
}
62+
63+
export function renderMapContains(renderMap: RenderMap, path: Path, value: RegExp | string): boolean {
64+
const content = getFromRenderMap(renderMap, path);
65+
return typeof value === 'string' ? content.includes(value) : value.test(content);
66+
}
67+
68+
export function writeRenderMap(renderMap: RenderMap, basePath: string): void {
69+
renderMap.forEach((content, relativePath) => {
70+
writeFile(`${basePath}/${relativePath}`, content);
71+
});
72+
}
73+
74+
export function writeRenderMapVisitor<TNodeKind extends NodeKind = NodeKind>(
75+
visitor: Visitor<RenderMap, TNodeKind>,
76+
basePath: Path,
77+
): Visitor<void, TNodeKind> {
78+
return mapVisitor(visitor, renderMap => writeRenderMap(renderMap, basePath));
79+
}

packages/renderers-core/src/writeRenderMapVisitor.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/renderers-js-umi/src/getRenderMapVisitor.ts

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
structTypeNodeFromInstructionArgumentNodes,
2222
VALUE_NODES,
2323
} from '@codama/nodes';
24-
import { RenderMap } from '@codama/renderers-core';
24+
import { addToRenderMap, mergeRenderMaps, RenderMap, renderMap } from '@codama/renderers-core';
2525
import {
2626
extendVisitor,
2727
getByteSizeVisitor,
@@ -128,7 +128,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
128128
}
129129

130130
return pipe(
131-
staticVisitor(() => new RenderMap()),
131+
staticVisitor(() => renderMap()),
132132
v =>
133133
extendVisitor(v, {
134134
visitAccount(node) {
@@ -229,7 +229,8 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
229229
}
230230
const hasVariableSeeds = pdaSeeds.filter(isNodeFilter('variablePdaSeedNode')).length > 0;
231231

232-
return new RenderMap().add(
232+
return addToRenderMap(
233+
renderMap(),
233234
`accounts/${camelCase(node.name)}.ts`,
234235
render('accountsPage.njk', {
235236
account: node,
@@ -257,7 +258,8 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
257258
`get${pascalCaseName}Serializer`,
258259
]);
259260

260-
return new RenderMap().add(
261+
return addToRenderMap(
262+
renderMap(),
261263
`types/${camelCase(node.name)}.ts`,
262264
render('definedTypesPage.njk', {
263265
definedType: node,
@@ -418,7 +420,8 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
418420
imports.add(getImportFrom(remainingAccounts.value), camelCase(remainingAccounts.value.name));
419421
}
420422

421-
return new RenderMap().add(
423+
return addToRenderMap(
424+
renderMap(),
422425
`instructions/${camelCase(node.name)}.ts`,
423426
render('instructionsPage.njk', {
424427
accounts,
@@ -458,40 +461,45 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
458461
...getDefinedTypeNodesToExtract(node.accounts, customAccountData),
459462
...getDefinedTypeNodesToExtract(node.instructions, customInstructionData),
460463
];
461-
const renderMap = new RenderMap()
462-
.mergeWith(...node.accounts.map(a => visit(a, self)))
463-
.mergeWith(...node.definedTypes.map(t => visit(t, self)))
464-
.mergeWith(...customDataDefinedType.map(t => visit(t, self)))
465-
.mergeWith(
464+
const renders = pipe(
465+
mergeRenderMaps([
466+
...node.accounts.map(a => visit(a, self)),
467+
...node.definedTypes.map(t => visit(t, self)),
468+
...customDataDefinedType.map(t => visit(t, self)),
466469
...getAllInstructionsWithSubs(node, {
467470
leavesOnly: !renderParentInstructions,
468471
}).map(ix => visit(ix, self)),
469-
)
470-
.add(
471-
`errors/${camelCase(node.name)}.ts`,
472-
render('errorsPage.njk', {
473-
errors: node.errors,
474-
imports: new ImportMap()
475-
.add('umi', ['ProgramError', 'Program'])
476-
.toString(dependencyMap),
477-
program: node,
478-
}),
479-
)
480-
.add(
481-
`programs/${camelCase(node.name)}.ts`,
482-
render('programsPage.njk', {
483-
imports: new ImportMap()
484-
.add('umi', ['ClusterFilter', 'Context', 'Program', 'PublicKey'])
485-
.add('errors', [
486-
`get${pascalCaseName}ErrorFromCode`,
487-
`get${pascalCaseName}ErrorFromName`,
488-
])
489-
.toString(dependencyMap),
490-
program: node,
491-
}),
492-
);
472+
]),
473+
r =>
474+
addToRenderMap(
475+
r,
476+
`errors/${camelCase(node.name)}.ts`,
477+
render('errorsPage.njk', {
478+
errors: node.errors,
479+
imports: new ImportMap()
480+
.add('umi', ['ProgramError', 'Program'])
481+
.toString(dependencyMap),
482+
program: node,
483+
}),
484+
),
485+
r =>
486+
addToRenderMap(
487+
r,
488+
`programs/${camelCase(node.name)}.ts`,
489+
render('programsPage.njk', {
490+
imports: new ImportMap()
491+
.add('umi', ['ClusterFilter', 'Context', 'Program', 'PublicKey'])
492+
.add('errors', [
493+
`get${pascalCaseName}ErrorFromCode`,
494+
`get${pascalCaseName}ErrorFromName`,
495+
])
496+
.toString(dependencyMap),
497+
program: node,
498+
}),
499+
),
500+
);
493501
program = null;
494-
return renderMap;
502+
return renders;
495503
},
496504

497505
visitRoot(node, { self }) {
@@ -517,29 +525,36 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}): Visitor<
517525
root: node,
518526
};
519527

520-
const map = new RenderMap();
528+
let renders = renderMap();
521529
if (hasAnythingToExport) {
522-
map.add('shared/index.ts', render('sharedPage.njk', ctx));
530+
renders = addToRenderMap(renders, 'shared/index.ts', render('sharedPage.njk', ctx));
523531
}
524532
if (programsToExport.length > 0) {
525-
map.add('programs/index.ts', render('programsIndex.njk', ctx)).add(
526-
'errors/index.ts',
527-
render('errorsIndex.njk', ctx),
533+
renders = pipe(
534+
renders,
535+
r => addToRenderMap(r, 'programs/index.ts', render('programsIndex.njk', ctx)),
536+
r => addToRenderMap(r, 'errors/index.ts', render('errorsIndex.njk', ctx)),
528537
);
529538
}
530539
if (accountsToExport.length > 0) {
531-
map.add('accounts/index.ts', render('accountsIndex.njk', ctx));
540+
renders = addToRenderMap(renders, 'accounts/index.ts', render('accountsIndex.njk', ctx));
532541
}
533542
if (instructionsToExport.length > 0) {
534-
map.add('instructions/index.ts', render('instructionsIndex.njk', ctx));
543+
renders = addToRenderMap(
544+
renders,
545+
'instructions/index.ts',
546+
render('instructionsIndex.njk', ctx),
547+
);
535548
}
536549
if (definedTypesToExport.length > 0) {
537-
map.add('types/index.ts', render('definedTypesIndex.njk', ctx));
550+
renders = addToRenderMap(renders, 'types/index.ts', render('definedTypesIndex.njk', ctx));
538551
}
539552

540-
return map
541-
.add('index.ts', render('rootIndex.njk', ctx))
542-
.mergeWith(...getAllPrograms(node).map(p => visit(p, self)));
553+
return pipe(
554+
renders,
555+
r => addToRenderMap(r, 'index.ts', render('rootIndex.njk', ctx)),
556+
r => mergeRenderMaps([r, ...getAllPrograms(node).map(p => visit(p, self))]),
557+
);
543558
},
544559
}),
545560
v => recordNodeStackVisitor(v, stack),

packages/renderers-js-umi/src/renderVisitor.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { deleteDirectory } from '@codama/renderers-core';
1+
import { deleteDirectory, mapRenderMapContentAsync, writeRenderMap } from '@codama/renderers-core';
22
import { LogLevel, throwValidatorItemsVisitor } from '@codama/validators';
33
import { rootNodeVisitor, visit } from '@codama/visitors-core';
44
import { Plugin } from 'prettier';
@@ -41,14 +41,14 @@ export function renderVisitor(path: string, options: RenderOptions = {}) {
4141
}
4242

4343
// Render the new files.
44-
const renderMap = visit(root, getRenderMapVisitor(options));
44+
let renderMap = visit(root, getRenderMapVisitor(options));
4545

4646
// Format the code.
4747
if (options.formatCode ?? true) {
4848
const prettierOptions = { ...DEFAULT_PRETTIER_OPTIONS, ...options.prettierOptions };
49-
await renderMap.mapContentAsync(code => format(code, prettierOptions));
49+
renderMap = await mapRenderMapContentAsync(renderMap, code => format(code, prettierOptions));
5050
}
5151

52-
renderMap.write(path);
52+
writeRenderMap(renderMap, path);
5353
});
5454
}

0 commit comments

Comments
 (0)