From ed061f517f9dfe7903d904b41c3176d936a9fce0 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 17 Apr 2024 21:48:03 +0000 Subject: [PATCH 1/6] Always return a shared frozen array from `getNodeChildren`, and mark all child-lists as readonly arrays. --- src/compiler/factory/nodeChildren.ts | 11 +++++++---- src/compiler/factory/nodeFactory.ts | 2 +- src/compiler/types.ts | 2 +- src/services/services.ts | 4 ++-- src/services/smartSelection.ts | 6 +++--- src/services/types.ts | 4 ++-- src/services/utilities.ts | 2 +- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/compiler/factory/nodeChildren.ts b/src/compiler/factory/nodeChildren.ts index 89fa8383b281d..1ea09af77f525 100644 --- a/src/compiler/factory/nodeChildren.ts +++ b/src/compiler/factory/nodeChildren.ts @@ -1,14 +1,17 @@ -import { Node } from "../_namespaces/ts"; +import { Node, isToken } from "../_namespaces/ts"; -const nodeChildren = new WeakMap(); +const nodeChildren = new WeakMap(); + +const emptyChildren: readonly Node[] = Object.freeze([]); /** @internal */ -export function getNodeChildren(node: Node): Node[] | undefined { +export function getNodeChildren(node: Node): readonly Node[] | undefined { + if (isToken(node)) return emptyChildren; return nodeChildren.get(node); } /** @internal */ -export function setNodeChildren(node: Node, children: Node[]): Node[] { +export function setNodeChildren(node: Node, children: readonly Node[]): readonly Node[] { nodeChildren.set(node, children); return children; } diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index c2b9f0d615005..78e3e3e5fd5ef 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -6209,7 +6209,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode } // @api - function createSyntaxList(children: Node[]) { + function createSyntaxList(children: readonly Node[]) { const node = createBaseNode(SyntaxKind.SyntaxList); setNodeChildren(node, children); return node; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d97dc608adcbf..45d4a41825d88 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -9006,7 +9006,7 @@ export interface NodeFactory { // Synthetic Nodes // /** @internal */ createSyntheticExpression(type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember): SyntheticExpression; - /** @internal */ createSyntaxList(children: Node[]): SyntaxList; + /** @internal */ createSyntaxList(children: readonly Node[]): SyntaxList; // // Transformation nodes diff --git a/src/services/services.ts b/src/services/services.ts index 18f0b4d151439..3997e97e83257 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -438,7 +438,7 @@ class NodeObject implements Node { return this.getChildren(sourceFile)[index]; } - public getChildren(sourceFile?: SourceFileLike): Node[] { + public getChildren(sourceFile?: SourceFileLike): readonly 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 getNodeChildren(this) ?? setNodeChildren(this, createChildren(this, sourceFile)); } @@ -473,7 +473,7 @@ class NodeObject implements Node { } } -function createChildren(node: Node, sourceFile: SourceFileLike | undefined): Node[] { +function createChildren(node: Node, sourceFile: SourceFileLike | undefined): readonly Node[] { if (!isNodeKind(node.kind)) { return emptyArray; } diff --git a/src/services/smartSelection.ts b/src/services/smartSelection.ts index d1cc5ca92472b..d726d177e682a 100644 --- a/src/services/smartSelection.ts +++ b/src/services/smartSelection.ts @@ -279,7 +279,7 @@ function getSelectionChildren(node: Node): readonly Node[] { * Groups sibling nodes together into their own SyntaxList if they * a) are adjacent, AND b) match a predicate function. */ -function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Node[] { +function groupChildren(children: readonly Node[], groupOn: (child: Node) => boolean): Node[] { const result: Node[] = []; let group: Node[] | undefined; for (const child of children) { @@ -315,7 +315,7 @@ function groupChildren(children: Node[], groupOn: (child: Node) => boolean): Nod * @param separateTrailingSemicolon If the last token is a semicolon, it will be returned as a separate * child rather than be included in the right-hand group. */ -function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): Node[] { +function splitChildren(children: readonly Node[], pivotOn: (child: Node) => boolean, separateTrailingSemicolon = true): readonly Node[] { if (children.length < 2) { return children; } @@ -336,7 +336,7 @@ function splitChildren(children: Node[], pivotOn: (child: Node) => boolean, sepa return separateLastToken ? result.concat(lastToken) : result; } -function createSyntaxList(children: Node[]): SyntaxList { +function createSyntaxList(children: readonly Node[]): SyntaxList { Debug.assertGreaterThanOrEqual(children.length, 1); return setTextRangePosEnd(parseNodeFactory.createSyntaxList(children), children[0].pos, last(children).end); } diff --git a/src/services/types.ts b/src/services/types.ts index f98d317d61dd5..3adcf0c39e65f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -49,9 +49,9 @@ declare module "../compiler/types" { getSourceFile(): SourceFile; getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; - getChildren(sourceFile?: SourceFile): Node[]; + getChildren(sourceFile?: SourceFile): readonly Node[]; /** @internal */ - getChildren(sourceFile?: SourceFileLike): Node[]; // eslint-disable-line @typescript-eslint/unified-signatures + getChildren(sourceFile?: SourceFileLike): readonly Node[]; // eslint-disable-line @typescript-eslint/unified-signatures getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; /** @internal */ getStart(sourceFile?: SourceFileLike, includeJsDocComment?: boolean): number; // eslint-disable-line @typescript-eslint/unified-signatures diff --git a/src/services/utilities.ts b/src/services/utilities.ts index dade1fe37bdd3..3fb95f79cb90f 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1830,7 +1830,7 @@ function findRightmostToken(n: Node, sourceFile: SourceFileLike): Node | undefin /** * Finds the rightmost child to the left of `children[exclusiveStartPosition]` which is a non-all-whitespace token or has constituent tokens. */ -function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number, sourceFile: SourceFileLike, parentKind: SyntaxKind): Node | undefined { +function findRightmostChildNodeWithTokens(children: readonly Node[], exclusiveStartPosition: number, sourceFile: SourceFileLike, parentKind: SyntaxKind): Node | undefined { for (let i = exclusiveStartPosition - 1; i >= 0; i--) { const child = children[i]; From e6ed5bf43b22155b233061aff8ea32a2bf69768e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 17 Apr 2024 21:48:14 +0000 Subject: [PATCH 2/6] Update API baseline. --- tests/baselines/reference/api/typescript.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ad1b05ed05eb7..be8af15e26d6a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4215,7 +4215,7 @@ declare namespace ts { getSourceFile(): SourceFile; getChildCount(sourceFile?: SourceFile): number; getChildAt(index: number, sourceFile?: SourceFile): Node; - getChildren(sourceFile?: SourceFile): Node[]; + getChildren(sourceFile?: SourceFile): readonly Node[]; getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; getFullStart(): number; getEnd(): number; From 6d15142432d0dee12c775e9d7766aa110f67d0b1 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Wed, 17 Apr 2024 21:52:45 +0000 Subject: [PATCH 3/6] Format. --- src/compiler/factory/nodeChildren.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/factory/nodeChildren.ts b/src/compiler/factory/nodeChildren.ts index 1ea09af77f525..8e8abc577e896 100644 --- a/src/compiler/factory/nodeChildren.ts +++ b/src/compiler/factory/nodeChildren.ts @@ -1,4 +1,7 @@ -import { Node, isToken } from "../_namespaces/ts"; +import { + isToken, + Node, +} from "../_namespaces/ts"; const nodeChildren = new WeakMap(); From 2a99b73cfa9a99db720d6c4fd037e58ad1bef35c Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 19 Apr 2024 13:31:17 -0700 Subject: [PATCH 4/6] Just use `emptyArray`. --- src/compiler/factory/nodeChildren.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/factory/nodeChildren.ts b/src/compiler/factory/nodeChildren.ts index 8e8abc577e896..e14eb9593bb47 100644 --- a/src/compiler/factory/nodeChildren.ts +++ b/src/compiler/factory/nodeChildren.ts @@ -1,15 +1,14 @@ import { + emptyArray, isToken, Node, } from "../_namespaces/ts"; const nodeChildren = new WeakMap(); -const emptyChildren: readonly Node[] = Object.freeze([]); - /** @internal */ export function getNodeChildren(node: Node): readonly Node[] | undefined { - if (isToken(node)) return emptyChildren; + if (isToken(node)) return emptyArray; return nodeChildren.get(node); } From 7bd902e332f613e2e1721a781bbee47ea10a447f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 19 Apr 2024 13:31:47 -0700 Subject: [PATCH 5/6] Remove duplicate check in `createChildren`. --- src/services/services.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 3997e97e83257..7096705f0c3e3 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -474,10 +474,6 @@ class NodeObject implements Node { } function createChildren(node: Node, sourceFile: SourceFileLike | undefined): readonly Node[] { - if (!isNodeKind(node.kind)) { - return emptyArray; - } - const children: Node[] = []; if (isJSDocCommentContainingNode(node)) { From 3a85df748bf66447f6c0bf0081eb77f79b3e087e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 19 Apr 2024 14:35:53 -0700 Subject: [PATCH 6/6] Switch to `isNodeKind`. --- src/compiler/factory/nodeChildren.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/factory/nodeChildren.ts b/src/compiler/factory/nodeChildren.ts index e14eb9593bb47..aa41f5955fb69 100644 --- a/src/compiler/factory/nodeChildren.ts +++ b/src/compiler/factory/nodeChildren.ts @@ -1,6 +1,6 @@ import { emptyArray, - isToken, + isNodeKind, Node, } from "../_namespaces/ts"; @@ -8,7 +8,8 @@ const nodeChildren = new WeakMap(); /** @internal */ export function getNodeChildren(node: Node): readonly Node[] | undefined { - if (isToken(node)) return emptyArray; + if (!isNodeKind(node.kind)) return emptyArray; + return nodeChildren.get(node); }