Skip to content

Commit 3ba3eb5

Browse files
langium ast-utils: pass 'trace' to the recursive calls of copyAstNode (#1995)
1 parent 0c9fac4 commit 3ba3eb5

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

packages/langium/src/utils/ast-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ export function copyAstNode<T extends AstNode = AstNode>(node: T, buildReference
278278
for (const [name, value] of Object.entries(node)) {
279279
if (!name.startsWith('$')) {
280280
if (isAstNode(value)) {
281-
copy[name] = copyAstNode(value, buildReference);
281+
copy[name] = copyAstNode(value, buildReference, trace);
282282
} else if (isReference(value)) {
283283
copy[name] = buildReference(
284284
copy,
@@ -291,7 +291,7 @@ export function copyAstNode<T extends AstNode = AstNode>(node: T, buildReference
291291
const copiedArray: unknown[] = [];
292292
for (const element of value) {
293293
if (isAstNode(element)) {
294-
copiedArray.push(copyAstNode(element, buildReference));
294+
copiedArray.push(copyAstNode(element, buildReference, trace));
295295
} else if (isReference(element)) {
296296
copiedArray.push(
297297
buildReference(

packages/langium/test/utils/ast-utils.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,69 @@ describe('AST Utils', () => {
9898
expectType<PartialTestNode>((null as unknown) as ResultType);
9999
expectType<ResultType>((null as unknown) as PartialTestNode);
100100
});
101+
102+
test('copyAstNode with trace should add all involved ast nodes to trace', () => {
103+
interface MyType extends AstNode {
104+
readonly $type: 'MyType';
105+
name?: string;
106+
singleChild?: MyType;
107+
children?: MyType[];
108+
buddy?: Reference<MyType>;
109+
}
110+
111+
const root: MyType = {
112+
$type: 'MyType',
113+
name: 'root',
114+
singleChild: {
115+
$type: 'MyType',
116+
name: 'singleChild',
117+
},
118+
children: [
119+
{
120+
$type: 'MyType',
121+
name: 'child1',
122+
buddy: {
123+
$refText: 'child2',
124+
get ref() { return root.children?.[1]; },
125+
}
126+
},
127+
{
128+
$type: 'MyType',
129+
name: 'child2',
130+
buddy: {
131+
$refText: 'child1',
132+
get ref() { return root.children?.[0]; },
133+
}
134+
}
135+
]
136+
};
137+
138+
const trace = new Map<AstNode, AstNode>();
139+
140+
// a simple reference builder function that identifies the to be referenced nodes
141+
// based on the trace map and the original reference
142+
// this approach might not work in general because of project-specific details
143+
// but it nicely illustrates the utility of the trace map and also checks it's proper population
144+
const buildReference: Parameters<typeof AstUtils.copyAstNode>['1'] = (_, _1, _2, _3, origRef) => ({
145+
...origRef,
146+
get ref() { return origRef.ref && trace.get(origRef.ref); }
147+
});
148+
149+
const copy = AstUtils.copyAstNode(root, buildReference, trace);
150+
151+
expect(trace.get(root)).toBe(copy);
152+
expect(trace.get(copy)).toBe(root);
153+
154+
expect(trace.get(root.singleChild!)).toBe(copy.singleChild);
155+
expect(trace.get(copy.singleChild!)).toBe(root.singleChild);
156+
157+
expect(trace.get(root.children![0])).toBe(copy.children![0]);
158+
expect(trace.get(copy.children![0])).toBe(root.children![0]);
159+
160+
expect(trace.get(root.children![1])).toBe(copy.children![1]);
161+
expect(trace.get(copy.children![1])).toBe(root.children![1]);
162+
163+
expect(copy.children![1].buddy!.ref).toBe(copy.children![0]);
164+
expect(copy.children![0].buddy!.ref).toBe(copy.children![1]);
165+
});
101166
});

0 commit comments

Comments
 (0)