Skip to content

Commit 1c2a4e6

Browse files
authored
Merge branch 'main' into feat/63701
2 parents 2e71d22 + e73c923 commit 1c2a4e6

1,113 files changed

Lines changed: 37543 additions & 2577 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ tsc/testdata/baselines/tmp
128128
tools/custom-gcl
129129
tools/custom-gcl.exe
130130
tools/custom-gcl.hash
131+
tools/custom-gcl.exe.hash
131132

132133
# Keep tracked text assets that overlap broad output patterns
133134
!tsc/testdata/baselines/reference/**/*.txt

packages/typescript/src/api/node/node.generated.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Code generated by tools/scripts/tsc/generate-encoder.ts. DO NOT EDIT.
22

33
import {
4+
getChildren,
5+
getFirstToken,
6+
getLastToken,
47
getTokenPosOfNode,
58
ModifierFlags,
69
type Node,
@@ -322,6 +325,26 @@ export class RemoteNode extends RemoteNodeBase implements Node {
322325
return sourceFile.text.substring(this.getStart(sourceFile), this.end);
323326
}
324327

328+
getChildCount(sourceFile?: SourceFile): number {
329+
return this.getChildren(sourceFile).length;
330+
}
331+
332+
getChildAt(index: number, sourceFile?: SourceFile): Node {
333+
return this.getChildren(sourceFile)[index];
334+
}
335+
336+
getChildren(sourceFile?: SourceFile): readonly Node[] {
337+
return getChildren(this as unknown as Node, sourceFile ?? this.getSourceFile());
338+
}
339+
340+
getFirstToken(sourceFile?: SourceFile): Node | undefined {
341+
return getFirstToken(this as unknown as Node, sourceFile ?? this.getSourceFile());
342+
}
343+
344+
getLastToken(sourceFile?: SourceFile): Node | undefined {
345+
return getLastToken(this as unknown as Node, sourceFile ?? this.getSourceFile());
346+
}
347+
325348
protected getString(index: number): string {
326349
const offsetStringTableOffsets = this.sourceFile._offsetStringTableOffsets;
327350
const start = this.view.getUint32(offsetStringTableOffsets + index * 4, true);

packages/typescript/src/ast/ast.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ export interface Node extends ReadonlyTextRange {
100100
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
101101
getFullText(sourceFile?: SourceFile): string;
102102
getText(sourceFile?: SourceFile): string;
103+
getChildCount(sourceFile?: SourceFile): number;
104+
getChildAt(index: number, sourceFile?: SourceFile): Node;
105+
getChildren(sourceFile?: SourceFile): readonly Node[];
106+
getFirstToken(sourceFile?: SourceFile): Node | undefined;
107+
getLastToken(sourceFile?: SourceFile): Node | undefined;
103108
}
104109

105110
export interface FileReference extends TextRange {
@@ -159,6 +164,8 @@ export interface SourceFile extends Node {
159164
getPositionOfLineAndCharacter(line: number, character: number): number;
160165
/** @internal */
161166
tokenCache?: Map<string, Node>;
167+
/** @internal */
168+
childrenCache?: Map<Node, readonly Node[]>;
162169
}
163170

164171
// ── Token hierarchy ──

packages/typescript/src/ast/astnav.ts

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ import type {
66
NodeArray,
77
SourceFile,
88
} from "./ast.ts";
9-
import { createToken } from "./factory.generated.ts";
9+
import {
10+
createSyntaxList,
11+
createToken,
12+
} from "./factory.generated.ts";
1013
import {
1114
isJSDocNodeKind,
1215
isKeywordKind,
1316
isPrivateIdentifier,
1417
isPropertyNameLiteral,
1518
isTokenKind,
1619
} from "./is.ts";
20+
import type { Scanner } from "./scanner.ts";
1721
import {
1822
createScanner,
1923
skipTrivia,
@@ -616,6 +620,156 @@ function getOrCreateToken(sourceFile: SourceFile, kind: SyntaxKind, pos: number,
616620
return token;
617621
}
618622

623+
const emptyArray: readonly Node[] = [];
624+
625+
function assertHasRealPosition(node: Node): void {
626+
if (node.pos < 0 || node.end < 0) {
627+
throw new Error("Node without a real position cannot be scanned and thus has no token nodes - use forEachChild and collect the result if that's fine");
628+
}
629+
}
630+
631+
export function getChildren(node: Node, sourceFile: SourceFile = node.getSourceFile()): readonly Node[] {
632+
// A SyntaxList already holds its (pre-materialized) children.
633+
if (node.kind === SyntaxKind.SyntaxList) {
634+
return (node as unknown as { children: readonly Node[]; }).children;
635+
}
636+
637+
if (isTokenKind(node.kind)) {
638+
// EndOfFile may carry leading JSDoc; every other token has no children. The EndOfFile
639+
// result must go through the cache: remote nodes rebuild .jsDoc on every access.
640+
if (node.kind !== SyntaxKind.EndOfFile) {
641+
return emptyArray;
642+
}
643+
}
644+
else {
645+
assertHasRealPosition(node);
646+
}
647+
648+
const cache = (sourceFile.childrenCache ??= new Map<Node, readonly Node[]>());
649+
const cached = cache.get(node);
650+
651+
if (cached !== undefined) {
652+
return cached;
653+
}
654+
655+
const children = node.kind === SyntaxKind.EndOfFile
656+
? node.jsDoc ?? emptyArray
657+
: createChildren(node, sourceFile);
658+
cache.set(node, children);
659+
return children;
660+
}
661+
662+
function createChildren(node: Node, sourceFile: SourceFile): readonly Node[] {
663+
const children: Node[] = [];
664+
665+
// Inside a JSDoc comment there are no real tokens to synthesize.
666+
if (shouldSkipChild(node)) {
667+
node.forEachChild(child => void children.push(child));
668+
return children;
669+
}
670+
671+
// One scanner serves every run of synthetic tokens materialized for this node.
672+
const scanner = createScanner(/*skipTrivia*/ true, sourceFile.languageVariant, sourceFile.text);
673+
let pos = node.pos;
674+
const processNode = (child: Node): undefined => {
675+
// Reparsed subtrees (JSDoc types materialized into the AST) have positions inside
676+
// the comment, not in this node's token range.
677+
if (child.flags & NodeFlags.Reparsed) {
678+
return;
679+
}
680+
addSyntheticNodes(children, pos, child.pos, node, sourceFile, scanner);
681+
children.push(child);
682+
pos = child.end;
683+
};
684+
const processNodes = (nodes: NodeArray<Node>): undefined => {
685+
if (nodes.length > 0 && !nodes.some(child => !(child.flags & NodeFlags.Reparsed))) {
686+
return;
687+
}
688+
addSyntheticNodes(children, pos, nodes.pos, node, sourceFile, scanner);
689+
children.push(createSyntaxListNode(nodes, node, sourceFile, scanner));
690+
pos = nodes.end;
691+
};
692+
693+
// JSDoc attached to the node is leading content, processed first.
694+
if (node.jsDoc) {
695+
for (const jsDoc of node.jsDoc) {
696+
processNode(jsDoc);
697+
}
698+
}
699+
pos = node.pos;
700+
node.forEachChild(processNode, processNodes);
701+
addSyntheticNodes(children, pos, node.end, node, sourceFile, scanner);
702+
return children;
703+
}
704+
705+
function addSyntheticNodes(children: Node[], pos: number, end: number, parent: Node, sourceFile: SourceFile, scanner: Scanner): void {
706+
if (pos >= end) {
707+
return;
708+
}
709+
scanner.resetTokenState(pos);
710+
scanner.scan();
711+
while (pos < end) {
712+
const token = scanner.getToken();
713+
const tokenEnd = scanner.getTokenEnd();
714+
if (tokenEnd <= end) {
715+
// An identifier should never appear as trivia between AST children; skip defensively.
716+
if (token !== SyntaxKind.Identifier) {
717+
children.push(getOrCreateToken(sourceFile, token, pos, tokenEnd, parent, scanner.getTokenFlags()));
718+
}
719+
}
720+
pos = tokenEnd;
721+
if (token === SyntaxKind.EndOfFile) {
722+
break;
723+
}
724+
scanner.scan();
725+
}
726+
}
727+
728+
function createSyntaxListNode(nodes: NodeArray<Node>, parent: Node, sourceFile: SourceFile, scanner: Scanner): Node {
729+
const listChildren: Node[] = [];
730+
let pos = nodes.pos;
731+
for (const child of nodes) {
732+
if (child.flags & NodeFlags.Reparsed) {
733+
continue;
734+
}
735+
addSyntheticNodes(listChildren, pos, child.pos, parent, sourceFile, scanner);
736+
listChildren.push(child);
737+
pos = child.end;
738+
}
739+
addSyntheticNodes(listChildren, pos, nodes.end, parent, sourceFile, scanner);
740+
const list = createSyntaxList(listChildren) as Mutable<Node>;
741+
list.pos = nodes.pos;
742+
list.end = nodes.end;
743+
list.parent = parent;
744+
return list as Node;
745+
}
746+
747+
export function getFirstToken(node: Node, sourceFile: SourceFile = node.getSourceFile()): Node | undefined {
748+
if (isTokenKind(node.kind)) {
749+
return undefined;
750+
}
751+
assertHasRealPosition(node);
752+
const children = getChildren(node, sourceFile);
753+
const child = children.find(kid => kid.kind < SyntaxKind.FirstJSDocNode || kid.kind > SyntaxKind.LastJSDocNode);
754+
if (child === undefined) {
755+
return undefined;
756+
}
757+
return child.kind < SyntaxKind.FirstNode ? child : getFirstToken(child, sourceFile);
758+
}
759+
760+
export function getLastToken(node: Node, sourceFile: SourceFile = node.getSourceFile()): Node | undefined {
761+
if (isTokenKind(node.kind)) {
762+
return undefined;
763+
}
764+
assertHasRealPosition(node);
765+
const children = getChildren(node, sourceFile);
766+
const child = children.length ? children[children.length - 1] : undefined;
767+
if (child === undefined) {
768+
return undefined;
769+
}
770+
return child.kind < SyntaxKind.FirstNode ? child : getLastToken(child, sourceFile);
771+
}
772+
619773
/** Binary search a node list for the node containing position. */
620774
function binarySearchNodeList(
621775
nodes: NodeArray<Node>,

packages/typescript/src/ast/factory.generated.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,12 @@ import type {
258258
WithStatement,
259259
YieldExpression,
260260
} from "./ast.ts";
261-
import { getTokenPosOfNode } from "./astnav.ts";
261+
import {
262+
getChildren,
263+
getFirstToken,
264+
getLastToken,
265+
getTokenPosOfNode,
266+
} from "./astnav.ts";
262267
import { cloneSourceFileData } from "./utils.ts";
263268
import {
264269
forEachChildOfJSDocParameterTag,
@@ -724,6 +729,26 @@ export class NodeObject {
724729
sourceFile ??= this.getSourceFile();
725730
return sourceFile.text.substring(this.getStart(sourceFile), this.end);
726731
}
732+
733+
getChildCount(sourceFile?: SourceFile): number {
734+
return this.getChildren(sourceFile).length;
735+
}
736+
737+
getChildAt(index: number, sourceFile?: SourceFile): Node {
738+
return this.getChildren(sourceFile)[index];
739+
}
740+
741+
getChildren(sourceFile?: SourceFile): readonly Node[] {
742+
return getChildren(this as unknown as Node, sourceFile ?? this.getSourceFile());
743+
}
744+
745+
getFirstToken(sourceFile?: SourceFile): Node | undefined {
746+
return getFirstToken(this as unknown as Node, sourceFile ?? this.getSourceFile());
747+
}
748+
749+
getLastToken(sourceFile?: SourceFile): Node | undefined {
750+
return getLastToken(this as unknown as Node, sourceFile ?? this.getSourceFile());
751+
}
727752
}
728753

729754
function isNodeArray<T extends Node>(array: readonly T[]): array is NodeArray<T> {

0 commit comments

Comments
 (0)