Skip to content

Commit c6d3dd6

Browse files
committed
Use NodePaths in NodeSelectors
1 parent 35dec6a commit c6d3dd6

10 files changed

+59
-48
lines changed

.changeset/gold-eyes-battle.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@codama/visitors-core': minor
3+
'@codama/visitors': minor
4+
---
5+
6+
Use `NodePaths` in `NodeSelectors`

packages/visitors-core/src/NodePath.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { assertIsNode, GetNodeFromKind, InstructionNode, isNode, Node, NodeKind,
22

33
export type NodePath<TNode extends Node | undefined = undefined> = TNode extends undefined
44
? readonly Node[]
5-
: readonly [...Node[], TNode];
5+
: readonly [...(readonly Node[]), TNode];
66

77
export function getLastNodeFromPath<TNode extends Node>(path: NodePath<TNode>): TNode {
88
return path[path.length - 1] as TNode;
@@ -49,22 +49,22 @@ export function getNodePathUntilLastNode<TKind extends NodeKind>(
4949
return path.slice(0, lastIndex + 1) as unknown as NodePath<GetNodeFromKind<TKind>>;
5050
}
5151

52-
function isNotEmptyNodePath(path: NodePath | null | undefined): path is NodePath<Node> {
52+
export function isFilledNodePath(path: NodePath | null | undefined): path is NodePath<Node> {
5353
return !!path && path.length > 0;
5454
}
5555

5656
export function isNodePath<TKind extends NodeKind>(
5757
path: NodePath | null | undefined,
5858
kind: TKind | TKind[],
5959
): path is NodePath<GetNodeFromKind<TKind>> {
60-
return isNode(isNotEmptyNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
60+
return isNode(isFilledNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
6161
}
6262

6363
export function assertIsNodePath<TKind extends NodeKind>(
6464
path: NodePath | null | undefined,
6565
kind: TKind | TKind[],
6666
): asserts path is NodePath<GetNodeFromKind<TKind>> {
67-
assertIsNode(isNotEmptyNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
67+
assertIsNode(isFilledNodePath(path) ? getLastNodeFromPath<Node>(path) : null, kind);
6868
}
6969

7070
export function nodePathToStringArray(path: NodePath): string[] {

packages/visitors-core/src/NodeSelector.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { camelCase, CamelCaseString, Node } from '@codama/nodes';
22

3-
import type { NodeStack } from './NodeStack';
3+
import { NodePath } from './NodePath';
44

55
export type NodeSelector = NodeSelectorFunction | NodeSelectorPath;
66

@@ -11,11 +11,11 @@ export type NodeSelector = NodeSelectorFunction | NodeSelectorPath;
1111
* - `[someNode]` matches a node of the given kind.
1212
* - `[someNode|someOtherNode]` matches a node with any of the given kind.
1313
* - `[someNode]someText` matches both the kind and the name of a node.
14-
* - `a.b.c` matches a node `c` such that its parent stack contains `a` and `b` in order (but not necessarily subsequent).
14+
* - `a.b.c` matches a node `c` such that its ancestors contains `a` and `b` in order (but not necessarily subsequent).
1515
*/
1616
export type NodeSelectorPath = string;
1717

18-
export type NodeSelectorFunction = (node: Node, stack: NodeStack) => boolean;
18+
export type NodeSelectorFunction = (path: NodePath) => boolean;
1919

2020
export const getNodeSelectorFunction = (selector: NodeSelector): NodeSelectorFunction => {
2121
if (typeof selector === 'function') return selector;
@@ -40,25 +40,29 @@ export const getNodeSelectorFunction = (selector: NodeSelector): NodeSelectorFun
4040
return true;
4141
};
4242

43-
const checkStack = (nodeStack: Node[], nodeSelectors: string[]): boolean => {
43+
const checkPath = (path: Node[], nodeSelectors: string[]): boolean => {
4444
if (nodeSelectors.length === 0) return true;
45-
if (nodeStack.length === 0) return false;
46-
const lastNode = nodeStack.pop() as Node;
45+
if (path.length === 0) return false;
46+
const lastNode = path.pop() as Node;
4747
const lastNodeSelector = nodeSelectors.pop() as string;
4848
return checkNode(lastNode, lastNodeSelector)
49-
? checkStack(nodeStack, nodeSelectors)
50-
: checkStack(nodeStack, [...nodeSelectors, lastNodeSelector]);
49+
? checkPath(path, nodeSelectors)
50+
: checkPath(path, [...nodeSelectors, lastNodeSelector]);
5151
};
5252

53-
const nodeSelectors = selector.split('.');
54-
const lastNodeSelector = nodeSelectors.pop() as string;
53+
const checkInitialPath = (path: Node[], nodeSelectors: string[]): boolean => {
54+
if (nodeSelectors.length === 0 || path.length === 0) return false;
55+
const lastNode = path.pop() as Node;
56+
const lastNodeSelector = nodeSelectors.pop() as string;
57+
return checkNode(lastNode, lastNodeSelector) && checkPath(path, nodeSelectors);
58+
};
5559

56-
return (node, stack) =>
57-
checkNode(node, lastNodeSelector) && checkStack(stack.getPath() as Node[], [...nodeSelectors]);
60+
const nodeSelectors = selector.split('.');
61+
return path => checkInitialPath([...path], [...nodeSelectors]);
5862
};
5963

6064
export const getConjunctiveNodeSelectorFunction = (selector: NodeSelector | NodeSelector[]): NodeSelectorFunction => {
6165
const selectors = Array.isArray(selector) ? selector : [selector];
6266
const selectorFunctions = selectors.map(getNodeSelectorFunction);
63-
return (node, stack) => selectorFunctions.every(fn => fn(node, stack));
67+
return path => selectorFunctions.every(fn => fn(path));
6468
};

packages/visitors-core/src/bottomUpTransformerVisitor.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { pipe } from './pipe';
88
import { recordNodeStackVisitor } from './recordNodeStackVisitor';
99
import { Visitor } from './visitor';
1010

11-
export type BottomUpNodeTransformer<TNode extends Node = Node> = (node: TNode, stack: NodeStack) => Node | null;
11+
export type BottomUpNodeTransformer = (node: Node, stack: NodeStack) => Node | null;
1212

13-
export type BottomUpNodeTransformerWithSelector<TNode extends Node = Node> = {
13+
export type BottomUpNodeTransformerWithSelector = {
1414
select: NodeSelector | NodeSelector[];
15-
transform: BottomUpNodeTransformer<TNode>;
15+
transform: BottomUpNodeTransformer;
1616
};
1717

1818
export function bottomUpTransformerVisitor<TNodeKind extends NodeKind = NodeKind>(
@@ -22,21 +22,21 @@ export function bottomUpTransformerVisitor<TNodeKind extends NodeKind = NodeKind
2222
const transformerFunctions = transformers.map((transformer): BottomUpNodeTransformer => {
2323
if (typeof transformer === 'function') return transformer;
2424
return (node, stack) =>
25-
getConjunctiveNodeSelectorFunction(transformer.select)(node, stack)
25+
getConjunctiveNodeSelectorFunction(transformer.select)(stack.getPath())
2626
? transformer.transform(node, stack)
2727
: node;
2828
});
2929

3030
const stack = new NodeStack();
3131
return pipe(
3232
identityVisitor(nodeKeys),
33-
v => recordNodeStackVisitor(v, stack),
3433
v =>
35-
interceptVisitor(v, (node, next) =>
36-
transformerFunctions.reduce(
37-
(acc, transformer) => (acc === null ? null : transformer(acc, stack.clone())),
34+
interceptVisitor(v, (node, next) => {
35+
return transformerFunctions.reduce(
36+
(acc, transformer) => (acc === null ? null : transformer(acc, stack)),
3837
next(node),
39-
),
40-
),
38+
);
39+
}),
40+
v => recordNodeStackVisitor(v, stack),
4141
);
4242
}

packages/visitors-core/src/topDownTransformerVisitor.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ import { pipe } from './pipe';
88
import { recordNodeStackVisitor } from './recordNodeStackVisitor';
99
import { Visitor } from './visitor';
1010

11-
export type TopDownNodeTransformer<TNode extends Node = Node> = <T extends TNode = TNode>(
12-
node: T,
13-
stack: NodeStack,
14-
) => T | null;
11+
export type TopDownNodeTransformer = <TNode extends Node>(node: TNode, stack: NodeStack) => TNode | null;
1512

16-
export type TopDownNodeTransformerWithSelector<TNode extends Node = Node> = {
13+
export type TopDownNodeTransformerWithSelector = {
1714
select: NodeSelector | NodeSelector[];
18-
transform: TopDownNodeTransformer<TNode>;
15+
transform: TopDownNodeTransformer;
1916
};
2017

2118
export function topDownTransformerVisitor<TNodeKind extends NodeKind = NodeKind>(
@@ -25,23 +22,23 @@ export function topDownTransformerVisitor<TNodeKind extends NodeKind = NodeKind>
2522
const transformerFunctions = transformers.map((transformer): TopDownNodeTransformer => {
2623
if (typeof transformer === 'function') return transformer;
2724
return (node, stack) =>
28-
getConjunctiveNodeSelectorFunction(transformer.select)(node, stack)
25+
getConjunctiveNodeSelectorFunction(transformer.select)(stack.getPath())
2926
? transformer.transform(node, stack)
3027
: node;
3128
});
3229

3330
const stack = new NodeStack();
3431
return pipe(
3532
identityVisitor(nodeKeys),
36-
v => recordNodeStackVisitor(v, stack),
3733
v =>
3834
interceptVisitor(v, (node, next) => {
3935
const appliedNode = transformerFunctions.reduce(
40-
(acc, transformer) => (acc === null ? null : transformer(acc, stack.clone())),
36+
(acc, transformer) => (acc === null ? null : transformer(acc, stack)),
4137
node as Parameters<typeof next>[0] | null,
4238
);
4339
if (appliedNode === null) return null;
4440
return next(appliedNode);
4541
}),
42+
v => recordNodeStackVisitor(v, stack),
4643
);
4744
}

packages/visitors-core/test/NodeSelector.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
instructionAccountNode,
1111
instructionArgumentNode,
1212
instructionNode,
13-
isNode,
1413
Node,
1514
numberTypeNode,
1615
optionTypeNode,
@@ -23,9 +22,11 @@ import {
2322
import { expect, test } from 'vitest';
2423

2524
import {
25+
getLastNodeFromPath,
2626
getNodeSelectorFunction,
2727
identityVisitor,
2828
interceptVisitor,
29+
isNodePath,
2930
NodeSelector,
3031
NodeStack,
3132
pipe,
@@ -196,12 +197,12 @@ const macro = (selector: NodeSelector, expectedSelected: Node[]) => {
196197
const selected = [] as Node[];
197198
const visitor = pipe(
198199
identityVisitor(),
199-
v => recordNodeStackVisitor(v, stack),
200200
v =>
201201
interceptVisitor(v, (node, next) => {
202-
if (selectorFunction(node, stack.clone())) selected.push(node);
202+
if (selectorFunction(stack.getPath())) selected.push(node);
203203
return next(node);
204204
}),
205+
v => recordNodeStackVisitor(v, stack),
205206
);
206207

207208
// When we visit the tree.
@@ -329,4 +330,7 @@ macro('[accountNode]gift.[publicKeyTypeNode|booleanTypeNode]', [
329330
]);
330331

331332
// Select using functions.
332-
macro(node => isNode(node, 'numberTypeNode') && node.format === 'u32', [tokenDelegatedAmountOption.prefix]);
333+
macro(
334+
path => isNodePath(path, 'numberTypeNode') && getLastNodeFromPath(path).format === 'u32',
335+
[tokenDelegatedAmountOption.prefix],
336+
);

packages/visitors-core/test/bottomUpTransformerVisitor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ test('it can transform nodes using multiple node selectors', () => {
9494
// - the second one selects all nodes with more than one ancestor.
9595
const visitor = bottomUpTransformerVisitor([
9696
{
97-
select: ['[numberTypeNode]', (_, nodeStack) => nodeStack.getPath().length > 1],
97+
select: ['[numberTypeNode]', path => path.length > 2],
9898
transform: () => stringTypeNode('utf8'),
9999
},
100100
]);

packages/visitors-core/test/topDownTransformerVisitor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ test('it can transform nodes using multiple node selectors', () => {
102102
// - the second one selects all nodes with more than one ancestor.
103103
const visitor = topDownTransformerVisitor([
104104
{
105-
select: ['[numberTypeNode]', (_, nodeStack) => nodeStack.getPath().length > 1],
105+
select: ['[numberTypeNode]', path => path.length > 2],
106106
transform: node => numberTypeNode('u64') as typeof node,
107107
},
108108
]);

packages/visitors/src/setFixedAccountSizesVisitor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { accountNode, assertIsNode, isNode } from '@codama/nodes';
1+
import { accountNode, assertIsNode } from '@codama/nodes';
22
import {
33
getByteSizeVisitor,
4+
getLastNodeFromPath,
5+
isNodePath,
46
LinkableDictionary,
57
NodeStack,
68
pipe,
@@ -18,7 +20,7 @@ export function setFixedAccountSizesVisitor() {
1820
const visitor = topDownTransformerVisitor(
1921
[
2022
{
21-
select: node => isNode(node, 'accountNode') && node.size === undefined,
23+
select: path => isNodePath(path, 'accountNode') && getLastNodeFromPath(path).size === undefined,
2224
transform: node => {
2325
assertIsNode(node, 'accountNode');
2426
const size = visit(node.data, byteSizeVisitor);

packages/visitors/src/unwrapTupleEnumWithSingleStructVisitor.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
CamelCaseString,
44
DefinedTypeNode,
55
enumStructVariantTypeNode,
6-
EnumTupleVariantTypeNode,
76
getAllDefinedTypes,
87
isNode,
98
resolveNestedTypeNode,
@@ -28,8 +27,7 @@ export function unwrapTupleEnumWithSingleStructVisitor(enumsOrVariantsToUnwrap:
2827
? [() => true]
2928
: enumsOrVariantsToUnwrap.map(selector => getNodeSelectorFunction(selector));
3029

31-
const shouldUnwrap = (node: EnumTupleVariantTypeNode, stack: NodeStack): boolean =>
32-
selectorFunctions.some(selector => selector(node, stack));
30+
const shouldUnwrap = (stack: NodeStack): boolean => selectorFunctions.some(selector => selector(stack.getPath()));
3331

3432
return rootNodeVisitor(root => {
3533
const typesToPotentiallyUnwrap: string[] = [];
@@ -44,7 +42,7 @@ export function unwrapTupleEnumWithSingleStructVisitor(enumsOrVariantsToUnwrap:
4442
select: '[enumTupleVariantTypeNode]',
4543
transform: (node, stack) => {
4644
assertIsNode(node, 'enumTupleVariantTypeNode');
47-
if (!shouldUnwrap(node, stack)) return node;
45+
if (!shouldUnwrap(stack)) return node;
4846
const tupleNode = resolveNestedTypeNode(node.tuple);
4947
if (tupleNode.items.length !== 1) return node;
5048
let item = tupleNode.items[0];

0 commit comments

Comments
 (0)