Skip to content

Commit a6fb4dc

Browse files
Create a SourceFile-level indirection on children maps, store SyntaxList children directly on nodes. (#59154)
1 parent 247a983 commit a6fb4dc

File tree

6 files changed

+67
-23
lines changed

6 files changed

+67
-23
lines changed

src/compiler/factory/nodeChildren.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,61 @@
11
import {
2+
Debug,
23
emptyArray,
34
isNodeKind,
45
Node,
6+
SourceFileLike,
7+
SyntaxKind,
8+
SyntaxList,
59
} from "../_namespaces/ts.js";
610

7-
const nodeChildren = new WeakMap<Node, readonly Node[] | undefined>();
11+
const sourceFileToNodeChildren = new WeakMap<SourceFileLike, WeakMap<Node, readonly Node[] | undefined>>();
812

913
/** @internal */
10-
export function getNodeChildren(node: Node): readonly Node[] | undefined {
11-
if (!isNodeKind(node.kind)) return emptyArray;
14+
export function getNodeChildren(node: Node, sourceFile: SourceFileLike): readonly Node[] | undefined {
15+
const kind = node.kind;
16+
if (!isNodeKind(kind)) {
17+
return emptyArray;
18+
}
19+
if (kind === SyntaxKind.SyntaxList) {
20+
return (node as SyntaxList)._children;
21+
}
1222

13-
return nodeChildren.get(node);
23+
return sourceFileToNodeChildren.get(sourceFile)?.get(node);
1424
}
1525

1626
/** @internal */
17-
export function setNodeChildren(node: Node, children: readonly Node[]): readonly Node[] {
18-
nodeChildren.set(node, children);
27+
export function setNodeChildren(node: Node, sourceFile: SourceFileLike, children: readonly Node[]): readonly Node[] {
28+
if (node.kind === SyntaxKind.SyntaxList) {
29+
// SyntaxList children are always eagerly created in the process of
30+
// creating their parent's `children` list. We shouldn't need to set them here.
31+
Debug.fail("Should not need to re-set the children of a SyntaxList.");
32+
}
33+
34+
let map = sourceFileToNodeChildren.get(sourceFile);
35+
if (map === undefined) {
36+
map = new WeakMap();
37+
sourceFileToNodeChildren.set(sourceFile, map);
38+
}
39+
map.set(node, children);
1940
return children;
2041
}
2142

2243
/** @internal */
23-
export function unsetNodeChildren(node: Node) {
24-
nodeChildren.delete(node);
44+
export function unsetNodeChildren(node: Node, origSourceFile: SourceFileLike) {
45+
if (node.kind === SyntaxKind.SyntaxList) {
46+
// Syntax lists are synthesized and we store their children directly on them.
47+
// They are a special case where we expect incremental parsing to toss them away entirely
48+
// if a change intersects with their containing parents.
49+
Debug.fail("Did not expect to unset the children of a SyntaxList.");
50+
}
51+
sourceFileToNodeChildren.get(origSourceFile)?.delete(node);
52+
}
53+
54+
/** @internal */
55+
export function transferSourceFileChildren(sourceFile: SourceFileLike, targetSourceFile: SourceFileLike) {
56+
const map = sourceFileToNodeChildren.get(sourceFile);
57+
if (map !== undefined) {
58+
sourceFileToNodeChildren.delete(sourceFile);
59+
sourceFileToNodeChildren.set(targetSourceFile, map);
60+
}
2561
}

src/compiler/factory/nodeFactory.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,6 @@ import {
388388
setEmitFlags,
389389
setIdentifierAutoGenerate,
390390
setIdentifierTypeArguments,
391-
setNodeChildren,
392391
setParent,
393392
setTextRange,
394393
ShorthandPropertyAssignment,
@@ -6212,7 +6211,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
62126211
// @api
62136212
function createSyntaxList(children: readonly Node[]) {
62146213
const node = createBaseNode<SyntaxList>(SyntaxKind.SyntaxList);
6215-
setNodeChildren(node, children);
6214+
node._children = children;
62166215
return node;
62176216
}
62186217

src/compiler/parser.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ import {
369369
tokenIsIdentifierOrKeywordOrGreaterThan,
370370
tokenToString,
371371
tracing,
372+
transferSourceFileChildren,
372373
TransformFlags,
373374
TryStatement,
374375
TupleTypeNode,
@@ -9969,6 +9970,7 @@ namespace IncrementalParser {
99699970
aggressiveChecks,
99709971
);
99719972
result.impliedNodeFormat = sourceFile.impliedNodeFormat;
9973+
transferSourceFileChildren(sourceFile, result);
99729974
return result;
99739975
}
99749976

@@ -10021,9 +10023,9 @@ namespace IncrementalParser {
1002110023
}
1002210024
}
1002310025

10024-
function moveElementEntirelyPastChangeRange(element: Node, isArray: false, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void;
10025-
function moveElementEntirelyPastChangeRange(element: NodeArray<Node>, isArray: true, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void;
10026-
function moveElementEntirelyPastChangeRange(element: Node | NodeArray<Node>, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) {
10026+
function moveElementEntirelyPastChangeRange(element: Node, origSourceFile: SourceFile, isArray: false, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void;
10027+
function moveElementEntirelyPastChangeRange(element: NodeArray<Node>, origSourceFile: SourceFile, isArray: true, delta: number, oldText: string, newText: string, aggressiveChecks: boolean): void;
10028+
function moveElementEntirelyPastChangeRange(element: Node | NodeArray<Node>, origSourceFile: SourceFile, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) {
1002710029
if (isArray) {
1002810030
visitArray(element as NodeArray<Node>);
1002910031
}
@@ -10040,7 +10042,7 @@ namespace IncrementalParser {
1004010042

1004110043
// Ditch any existing LS children we may have created. This way we can avoid
1004210044
// moving them forward.
10043-
unsetNodeChildren(node);
10045+
unsetNodeChildren(node, origSourceFile);
1004410046

1004510047
setTextRangePosEnd(node, node.pos + delta, node.end + delta);
1004610048

@@ -10187,7 +10189,7 @@ namespace IncrementalParser {
1018710189
if (child.pos > changeRangeOldEnd) {
1018810190
// Node is entirely past the change range. We need to move both its pos and
1018910191
// end, forward or backward appropriately.
10190-
moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks);
10192+
moveElementEntirelyPastChangeRange(child, sourceFile, /*isArray*/ false, delta, oldText, newText, aggressiveChecks);
1019110193
return;
1019210194
}
1019310195

@@ -10197,7 +10199,7 @@ namespace IncrementalParser {
1019710199
const fullEnd = child.end;
1019810200
if (fullEnd >= changeStart) {
1019910201
markAsIntersectingIncrementalChange(child);
10200-
unsetNodeChildren(child);
10202+
unsetNodeChildren(child, sourceFile);
1020110203

1020210204
// Adjust the pos or end (or both) of the intersecting element accordingly.
1020310205
adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
@@ -10220,7 +10222,7 @@ namespace IncrementalParser {
1022010222
if (array.pos > changeRangeOldEnd) {
1022110223
// Array is entirely after the change range. We need to move it, and move any of
1022210224
// its children.
10223-
moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks);
10225+
moveElementEntirelyPastChangeRange(array, sourceFile, /*isArray*/ true, delta, oldText, newText, aggressiveChecks);
1022410226
return;
1022510227
}
1022610228

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9816,6 +9816,12 @@ export interface DiagnosticCollection {
98169816
// SyntaxKind.SyntaxList
98179817
export interface SyntaxList extends Node {
98189818
kind: SyntaxKind.SyntaxList;
9819+
9820+
// Unlike other nodes which may or may not have their child nodes calculated,
9821+
// the entire purpose of a SyntaxList is to hold child nodes.
9822+
// Instead of using the WeakMap machinery in `nodeChildren.ts`,
9823+
// we just store the children directly on the SyntaxList.
9824+
/** @internal */ _children: readonly Node[];
98199825
}
98209826

98219827
// dprint-ignore

src/compiler/utilities.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, inclu
11871187

11881188
if (isJSDocNode(node) || node.kind === SyntaxKind.JsxText) {
11891189
// JsxText cannot actually contain comments, even though the scanner will think it sees comments
1190-
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
1190+
return skipTrivia((sourceFile ?? getSourceFileOfNode(node)).text, node.pos, /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
11911191
}
11921192

11931193
if (includeJsDoc && hasJSDocNodes(node)) {
@@ -1199,14 +1199,15 @@ export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, inclu
11991199
// trivia for the list, we may have skipped the JSDocComment as well. So we should process its
12001200
// first child to determine the actual position of its first token.
12011201
if (node.kind === SyntaxKind.SyntaxList) {
1202-
const first = firstOrUndefined(getNodeChildren(node));
1202+
sourceFile ??= getSourceFileOfNode(node);
1203+
const first = firstOrUndefined(getNodeChildren(node, sourceFile));
12031204
if (first) {
12041205
return getTokenPosOfNode(first, sourceFile, includeJsDoc);
12051206
}
12061207
}
12071208

12081209
return skipTrivia(
1209-
(sourceFile || getSourceFileOfNode(node)).text,
1210+
(sourceFile ?? getSourceFileOfNode(node)).text,
12101211
node.pos,
12111212
/*stopAfterLineBreak*/ false,
12121213
/*stopAtComments*/ false,

src/services/services.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,9 +457,9 @@ class NodeObject<TKind extends SyntaxKind> implements Node {
457457
return this.getChildren(sourceFile)[index];
458458
}
459459

460-
public getChildren(sourceFile?: SourceFileLike): readonly Node[] {
460+
public getChildren(sourceFile: SourceFileLike = getSourceFileOfNode(this)): readonly Node[] {
461461
this.assertHasRealPosition("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine");
462-
return getNodeChildren(this) ?? setNodeChildren(this, createChildren(this, sourceFile));
462+
return getNodeChildren(this, sourceFile) ?? setNodeChildren(this, sourceFile, createChildren(this, sourceFile));
463463
}
464464

465465
public getFirstToken(sourceFile?: SourceFileLike): Node | undefined {
@@ -558,7 +558,7 @@ function createSyntaxList(nodes: NodeArray<Node>, parent: Node): Node {
558558
pos = node.end;
559559
}
560560
addSyntheticNodes(children, pos, nodes.end, parent);
561-
setNodeChildren(list, children);
561+
list._children = children;
562562
return list;
563563
}
564564

0 commit comments

Comments
 (0)