diff --git a/src/compiler/_namespaces/ts.ts b/src/compiler/_namespaces/ts.ts index 1ce7091080e7a..5549643b269ab 100644 --- a/src/compiler/_namespaces/ts.ts +++ b/src/compiler/_namespaces/ts.ts @@ -21,6 +21,7 @@ export * from "../factory/nodeFactory"; export * from "../factory/emitNode"; export * from "../factory/emitHelpers"; export * from "../factory/nodeTests"; +export * from "../factory/nodeChildren"; export * from "../factory/utilities"; export * from "../factory/utilitiesPublic"; export * from "../parser"; diff --git a/src/compiler/factory/nodeChildren.ts b/src/compiler/factory/nodeChildren.ts new file mode 100644 index 0000000000000..1c2e9f6683cc6 --- /dev/null +++ b/src/compiler/factory/nodeChildren.ts @@ -0,0 +1,27 @@ +import { Node } from "../_namespaces/ts"; + +const nodeChildren = new WeakMap(); + +/** @internal */ +export function getNodeChildren(node: Node): Node[] | undefined { + return nodeChildren.get(node); +} + +/** @internal */ +export function setNodeChildren(node: Node, children: Node[]) { + nodeChildren.set(node, children); +} + +/** @internal */ +export function unsetNodeChildren(node: Node) { + nodeChildren.delete(node); +} + +/** @internal */ +export function getOrSetNodeChildren(node: Node, fn: () => Node[]): Node[] { + let children = getNodeChildren(node); + if (children === undefined) { + setNodeChildren(node, children = fn()); + } + return children; +} diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 76e084c90d8f0..c2b9f0d615005 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -387,6 +387,7 @@ import { setEmitFlags, setIdentifierAutoGenerate, setIdentifierTypeArguments, + setNodeChildren, setParent, setTextRange, ShorthandPropertyAssignment, @@ -6210,7 +6211,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode // @api function createSyntaxList(children: Node[]) { const node = createBaseNode(SyntaxKind.SyntaxList); - node._children = children; + setNodeChildren(node, children); return node; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 3afd9a5ed6120..35366ac9d6513 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -387,6 +387,7 @@ import { unescapeLeadingUnderscores, UnionOrIntersectionTypeNode, UnionTypeNode, + unsetNodeChildren, UpdateExpression, VariableDeclaration, VariableDeclarationList, @@ -10002,9 +10003,7 @@ namespace IncrementalParser { // Ditch any existing LS children we may have created. This way we can avoid // moving them forward. - if (node._children) { - node._children = undefined; - } + unsetNodeChildren(node); setTextRangePosEnd(node, node.pos + delta, node.end + delta); @@ -10160,7 +10159,7 @@ namespace IncrementalParser { const fullEnd = child.end; if (fullEnd >= changeStart) { child.intersectsChange = true; - child._children = undefined; + unsetNodeChildren(child); // Adjust the pos or end (or both) of the intersecting element accordingly. adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta); @@ -10349,7 +10348,6 @@ namespace IncrementalParser { export interface IncrementalNode extends Node, IncrementalElement { hasBeenIncrementallyParsed: boolean; - _children: Node[] | undefined; } interface IncrementalNodeArray extends NodeArray, IncrementalElement { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 05ec88e3f62d2..2cd73619a4d01 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -9666,7 +9666,6 @@ export interface DiagnosticCollection { // SyntaxKind.SyntaxList export interface SyntaxList extends Node { kind: SyntaxKind.SyntaxList; - _children: Node[]; } // dprint-ignore diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2069aa77b6760..50d3bcec3b753 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -187,6 +187,7 @@ import { getLineStarts, getModeForUsageLocation, getNameOfDeclaration, + getNodeChildren, getNormalizedAbsolutePath, getNormalizedPathComponents, getOwnKeys, @@ -510,7 +511,6 @@ import { SymbolFlags, SymbolTable, SyntaxKind, - SyntaxList, TaggedTemplateExpression, TemplateExpression, TemplateLiteral, @@ -1161,8 +1161,11 @@ export function getTokenPosOfNode(node: Node, sourceFile?: SourceFileLike, inclu // the syntax list itself considers them as normal trivia. Therefore if we simply skip // trivia for the list, we may have skipped the JSDocComment as well. So we should process its // first child to determine the actual position of its first token. - if (node.kind === SyntaxKind.SyntaxList && (node as SyntaxList)._children.length > 0) { - return getTokenPosOfNode((node as SyntaxList)._children[0], sourceFile, includeJsDoc); + if (node.kind === SyntaxKind.SyntaxList) { + const first = firstOrUndefined(getNodeChildren(node)); + if (first) { + return getTokenPosOfNode(first, sourceFile, includeJsDoc); + } } return skipTrivia( diff --git a/src/services/services.ts b/src/services/services.ts index 82a8106d1e8e7..9371bb1ec46ce 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -109,6 +109,7 @@ import { getNonAssignedNameOfDeclaration, getNormalizedAbsolutePath, getObjectFlags, + getOrSetNodeChildren, getQuotePreference, getScriptKind, getSetExternalModuleIndicator, @@ -266,6 +267,7 @@ import { ScriptTarget, SelectionRange, SemanticClassificationFormat, + setNodeChildren, setObjectAllocator, Signature, SignatureDeclaration, @@ -357,7 +359,6 @@ class NodeObject implements Node { public symbol!: Symbol; // Actually optional, but it was too annoying to access `node.symbol!` everywhere since in many cases we know it must be defined public jsDoc?: JSDoc[]; public original?: Node; - private _children: Node[] | undefined; public id?: number; public emitNode?: EmitNode; @@ -373,7 +374,6 @@ class NodeObject implements Node { this.parent = undefined!; this.original = undefined; this.emitNode = undefined; - this._children = undefined; } private assertHasRealPosition(message?: string) { @@ -438,7 +438,7 @@ class NodeObject implements Node { public getChildren(sourceFile?: SourceFileLike): Node[] { 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"); - return this._children || (this._children = createChildren(this, sourceFile)); + return getOrSetNodeChildren(this, () => createChildren(this, sourceFile)); } public getFirstToken(sourceFile?: SourceFileLike): Node | undefined { @@ -533,14 +533,15 @@ function addSyntheticNodes(nodes: Node[], pos: number, end: number, parent: Node function createSyntaxList(nodes: NodeArray, parent: Node): Node { const list = createNode(SyntaxKind.SyntaxList, nodes.pos, nodes.end, parent) as any as SyntaxList; - list._children = []; + const children: Node[] = []; let pos = nodes.pos; for (const node of nodes) { - addSyntheticNodes(list._children, pos, node.pos, parent); - list._children.push(node); + addSyntheticNodes(children, pos, node.pos, parent); + children.push(node); pos = node.end; } - addSyntheticNodes(list._children, pos, nodes.end, parent); + addSyntheticNodes(children, pos, nodes.end, parent); + setNodeChildren(list, children); return list; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index dd7852454197c..3adacffaa8f74 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -7985,7 +7985,6 @@ declare namespace ts { } interface SyntaxList extends Node { kind: SyntaxKind.SyntaxList; - _children: Node[]; } enum ListFormat { None = 0,