Skip to content

Commit 61ceaa8

Browse files
committed
Refactor NodeStack and NodePath
1 parent 449b176 commit 61ceaa8

14 files changed

+82
-90
lines changed

packages/renderers-js/src/getRenderMapVisitor.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { RenderMap } from '@codama/renderers-core';
1717
import {
1818
extendVisitor,
19+
findProgramNodeFromPath,
1920
getResolvedInstructionInputsVisitor,
2021
LinkableDictionary,
2122
NodeStack,
@@ -141,13 +142,14 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
141142
v =>
142143
extendVisitor(v, {
143144
visitAccount(node) {
144-
if (!stack.getProgram()) {
145+
const accountPath = stack.getPath('accountNode');
146+
if (!findProgramNodeFromPath(accountPath)) {
145147
throw new Error('Account must be visited inside a program.');
146148
}
147149

148150
const scope = {
149151
...globalScope,
150-
accountPath: stack.getPath('accountNode'),
152+
accountPath,
151153
typeManifest: visit(node, typeManifestVisitor),
152154
};
153155

@@ -221,7 +223,8 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
221223
},
222224

223225
visitInstruction(node) {
224-
if (!stack.getProgram()) {
226+
const instructionPath = stack.getPath('instructionNode');
227+
if (!findProgramNodeFromPath(instructionPath)) {
225228
throw new Error('Instruction must be visited inside a program.');
226229
}
227230

@@ -236,7 +239,7 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
236239
strict: nameApi.dataType(instructionExtraName),
237240
}),
238241
),
239-
instructionPath: stack.getPath('instructionNode'),
242+
instructionPath,
240243
renamedArgs: getRenamedArgsMap(node),
241244
resolvedInputs: visit(node, resolvedInstructionInputVisitor),
242245
};
@@ -289,11 +292,12 @@ export function getRenderMapVisitor(options: GetRenderMapOptions = {}) {
289292
},
290293

291294
visitPda(node) {
292-
if (!stack.getProgram()) {
295+
const pdaPath = stack.getPath('pdaNode');
296+
if (!findProgramNodeFromPath(pdaPath)) {
293297
throw new Error('Account must be visited inside a program.');
294298
}
295299

296-
const scope = { ...globalScope, pdaPath: stack.getPath('pdaNode') };
300+
const scope = { ...globalScope, pdaPath };
297301
const pdaFunctionFragment = getPdaFunctionFragment(scope);
298302
const imports = new ImportMap().mergeWith(pdaFunctionFragment);
299303

packages/renderers-js/src/getTypeManifestVisitor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '@codama/nodes';
1616
import {
1717
extendVisitor,
18+
findLastNodeFromPath,
1819
LinkableDictionary,
1920
NodeStack,
2021
pipe,
@@ -830,8 +831,9 @@ export function getTypeManifestVisitor(input: {
830831
}
831832

832833
// Check if we are inside an instruction or account to use discriminator constants when available.
833-
const instructionNode = stack.find('instructionNode');
834-
const accountNode = stack.find('accountNode');
834+
const parentPath = stack.getPath();
835+
const instructionNode = findLastNodeFromPath(parentPath, 'instructionNode');
836+
const accountNode = findLastNodeFromPath(parentPath, 'accountNode');
835837
const discriminatorPrefix = instructionNode ? instructionNode.name : accountNode?.name;
836838
const discriminators =
837839
(instructionNode ? instructionNode.discriminators : accountNode?.discriminators) ?? [];

packages/validators/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type ValidationItem = {
3737
// The node that the validation item is related to.
3838
node: Node;
3939
// The path of nodes that led to the node above (including the node itself).
40-
path: readonly Node[];
40+
path: NodePath;
4141
};
4242
```
4343

packages/validators/src/ValidationItem.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Node } from '@codama/nodes';
2-
import { NodeStack } from '@codama/visitors-core';
2+
import { NodePath, NodeStack } from '@codama/visitors-core';
33

44
export const LOG_LEVELS = ['debug', 'trace', 'info', 'warn', 'error'] as const;
55
export type LogLevel = (typeof LOG_LEVELS)[number];
@@ -8,20 +8,20 @@ export type ValidationItem = {
88
level: LogLevel;
99
message: string;
1010
node: Node;
11-
path: readonly Node[];
11+
path: NodePath;
1212
};
1313

1414
export function validationItem(
1515
level: LogLevel,
1616
message: string,
1717
node: Node,
18-
stack: Node[] | NodeStack,
18+
path: NodePath | NodeStack,
1919
): ValidationItem {
2020
return {
2121
level,
2222
message,
2323
node,
24-
path: Array.isArray(stack) ? [...stack] : stack.all(),
24+
path: Array.isArray(path) ? path : (path as NodeStack).getPath(),
2525
};
2626
}
2727

packages/validators/src/getValidationItemsVisitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function getValidationItemsVisitor(): Visitor<readonly ValidationItem[]>
4747
const items = [] as ValidationItem[];
4848
if (!node.name) {
4949
items.push(validationItem('error', 'Pointing to a defined type with no name.', node, stack));
50-
} else if (!linkables.has(stack.getPath())) {
50+
} else if (!linkables.has(stack.getPath(node.kind))) {
5151
items.push(
5252
validationItem(
5353
'error',

packages/visitors-core/README.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -442,19 +442,11 @@ const lastNode = nodeStack.pop();
442442
// Peek at the last node in the stack.
443443
const lastNode = nodeStack.peek();
444444
// Get all the nodes in the stack as an array.
445-
const nodes = nodeStack.all();
446-
// Get the closest node in the stack matching one or several node kinds.
447-
const nodes = nodeStack.find('accountNode');
448-
// Get the closest program node in the stack.
449-
const nodes = nodeStack.getProgram();
450-
// Get the closest instruction node in the stack.
451-
const nodes = nodeStack.getInstruction();
445+
const path = nodeStack.getPath();
452446
// Check if the stack is empty.
453447
const isEmpty = nodeStack.isEmpty();
454448
// Clone the stack.
455449
const clonedStack = nodeStack.clone();
456-
// Get a string representation of the stack.
457-
const stackString = nodeStack.toString();
458450
```
459451

460452
### `recordNodeStackVisitor`
@@ -470,7 +462,7 @@ const visitor = pipe(
470462
v => recordNodeStackVisitor(v, stack),
471463
v =>
472464
interceptVisitor(v, (node, next) => {
473-
console.log(stack.clone().toString());
465+
console.log(nodePathToString(stack.getPath()));
474466
return next(node);
475467
}),
476468
);

packages/visitors-core/src/NodePath.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { assertIsNode, GetNodeFromKind, InstructionNode, isNode, Node, NodeKind, ProgramNode } from '@codama/nodes';
22

3-
export type NodePath<TNode extends Node = Node> = readonly [...Node[], TNode];
3+
export type NodePath<TNode extends Node | undefined = undefined> = TNode extends undefined
4+
? readonly Node[]
5+
: readonly [...Node[], TNode];
46

57
export function getLastNodeFromPath<TNode extends Node>(path: NodePath<TNode>): TNode {
68
return path[path.length - 1] as TNode;
@@ -47,16 +49,30 @@ export function getNodePathUntilLastNode<TKind extends NodeKind>(
4749
return path.slice(0, lastIndex + 1) as unknown as NodePath<GetNodeFromKind<TKind>>;
4850
}
4951

52+
function isNotEmptyNodePath(path: NodePath | null | undefined): path is NodePath<Node> {
53+
return !!path && path.length > 0;
54+
}
55+
5056
export function isNodePath<TKind extends NodeKind>(
5157
path: NodePath | null | undefined,
5258
kind: TKind | TKind[],
5359
): path is NodePath<GetNodeFromKind<TKind>> {
54-
return isNode(path ? getLastNodeFromPath(path) : null, kind);
60+
return isNode(isNotEmptyNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
5561
}
5662

5763
export function assertIsNodePath<TKind extends NodeKind>(
5864
path: NodePath | null | undefined,
5965
kind: TKind | TKind[],
6066
): asserts path is NodePath<GetNodeFromKind<TKind>> {
61-
assertIsNode(path ? getLastNodeFromPath(path) : null, kind);
67+
assertIsNode(isNotEmptyNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
68+
}
69+
70+
export function nodePathToStringArray(path: NodePath): string[] {
71+
return path.map((node): string => {
72+
return 'name' in node ? `[${node.kind}]${node.name}` : `[${node.kind}]`;
73+
});
74+
}
75+
76+
export function nodePathToString(path: NodePath): string {
77+
return nodePathToStringArray(path).join(' > ');
6278
}

packages/visitors-core/src/NodeSelector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export const getNodeSelectorFunction = (selector: NodeSelector): NodeSelectorFun
5353
const nodeSelectors = selector.split('.');
5454
const lastNodeSelector = nodeSelectors.pop() as string;
5555

56-
return (node, stack) => checkNode(node, lastNodeSelector) && checkStack(stack.all() as Node[], [...nodeSelectors]);
56+
return (node, stack) =>
57+
checkNode(node, lastNodeSelector) && checkStack(stack.getPath() as Node[], [...nodeSelectors]);
5758
};
5859

5960
export const getConjunctiveNodeSelectorFunction = (selector: NodeSelector | NodeSelector[]): NodeSelectorFunction => {
Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import {
2-
assertIsNode,
3-
GetNodeFromKind,
4-
InstructionNode,
5-
Node,
6-
NodeKind,
7-
ProgramNode,
8-
REGISTERED_NODE_KINDS,
9-
} from '@codama/nodes';
1+
import { GetNodeFromKind, Node, NodeKind } from '@codama/nodes';
102

11-
import { findLastNodeFromPath, NodePath } from './NodePath';
3+
import { assertIsNodePath, NodePath } from './NodePath';
4+
5+
type MutableNodePath = Node[];
126

137
export class NodeStack {
148
/**
@@ -21,78 +15,56 @@ export class NodeStack {
2115
*
2216
* There must at least be one stack in the heap at all times.
2317
*/
24-
private readonly heap: [...Node[][], Node[]];
18+
private readonly heap: [...MutableNodePath[], MutableNodePath];
2519

26-
constructor(...heap: readonly [...(readonly (readonly Node[])[]), readonly Node[]] | readonly []) {
27-
this.heap = heap.length === 0 ? [[]] : ([...heap.map(nodes => [...nodes])] as [...Node[][], Node[]]);
20+
constructor(...heap: readonly [...(readonly NodePath[]), NodePath] | readonly []) {
21+
this.heap =
22+
heap.length === 0 ? [[]] : ([...heap.map(nodes => [...nodes])] as [...MutableNodePath[], MutableNodePath]);
2823
}
2924

30-
public get stack(): Node[] {
25+
private get currentPath(): MutableNodePath {
3126
return this.heap[this.heap.length - 1];
3227
}
3328

3429
public push(node: Node): void {
35-
this.stack.push(node);
30+
this.currentPath.push(node);
3631
}
3732

3833
public pop(): Node | undefined {
39-
return this.stack.pop();
34+
return this.currentPath.pop();
4035
}
4136

4237
public peek(): Node | undefined {
43-
return this.isEmpty() ? undefined : this.stack[this.stack.length - 1];
38+
return this.isEmpty() ? undefined : this.currentPath[this.currentPath.length - 1];
4439
}
4540

46-
public pushStack(newStack: readonly Node[] = []): void {
47-
this.heap.push([...newStack]);
41+
public pushPath(newPath: NodePath = []): void {
42+
this.heap.push([...newPath]);
4843
}
4944

50-
public popStack(): readonly Node[] {
51-
const oldStack = this.heap.pop() as Node[];
45+
public popPath(): NodePath {
5246
if (this.heap.length === 0) {
5347
// TODO: Coded error
5448
throw new Error('The heap of stacks can never be empty.');
5549
}
56-
return [...oldStack] as readonly Node[];
57-
}
58-
59-
public find<TKind extends NodeKind>(kind: TKind | TKind[]): GetNodeFromKind<TKind> | undefined {
60-
return findLastNodeFromPath([...this.stack] as unknown as NodePath<GetNodeFromKind<TKind>>, kind);
61-
}
62-
63-
public getProgram(): ProgramNode | undefined {
64-
return this.find('programNode');
65-
}
66-
67-
public getInstruction(): InstructionNode | undefined {
68-
return this.find('instructionNode');
50+
return [...this.heap.pop()!];
6951
}
7052

71-
public all(): readonly Node[] {
72-
return [...this.stack];
73-
}
74-
75-
public getPath<TKind extends NodeKind>(kind?: TKind | TKind[]): NodePath<GetNodeFromKind<TKind>> {
76-
const node = this.peek();
77-
assertIsNode(node, kind ?? REGISTERED_NODE_KINDS);
78-
return [...this.stack] as unknown as NodePath<GetNodeFromKind<TKind>>;
53+
public getPath(): NodePath;
54+
public getPath<TKind extends NodeKind>(kind: TKind | TKind[]): NodePath<GetNodeFromKind<TKind>>;
55+
public getPath<TKind extends NodeKind>(kind?: TKind | TKind[]): NodePath {
56+
const path = [...this.currentPath];
57+
if (kind) {
58+
assertIsNodePath(path, kind);
59+
}
60+
return path;
7961
}
8062

8163
public isEmpty(): boolean {
82-
return this.stack.length === 0;
64+
return this.currentPath.length === 0;
8365
}
8466

8567
public clone(): NodeStack {
8668
return new NodeStack(...this.heap);
8769
}
88-
89-
public toString(): string {
90-
return this.toStringArray().join(' > ');
91-
}
92-
93-
public toStringArray(): string[] {
94-
return this.stack.map((node): string => {
95-
return 'name' in node ? `[${node.kind}]${node.name}` : `[${node.kind}]`;
96-
});
97-
}
9870
}

packages/visitors-core/src/recordLinkablesVisitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function getRecordLinkablesVisitor<TNodeKind extends NodeKind>(
1818
v =>
1919
interceptVisitor(v, (node, next) => {
2020
if (isNode(node, LINKABLE_NODES)) {
21-
linkables.recordPath(stack.getPath());
21+
linkables.recordPath(stack.getPath(LINKABLE_NODES));
2222
}
2323
return next(node);
2424
}),

0 commit comments

Comments
 (0)