mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-10-29 00:45:56 +08:00
Add childrenIter method to API nodes (#64302)
This commit is contained in:
1 parent
c975de5011
commit
5bb0fe6987
8 files changed
+2716
-5
No files matched your search
@@ -69,6 +69,10 @@ export class RemoteNodeList extends Array<RemoteNode> implements NodeArray<Remot
|
||||
return this.view.getUint32(this._byteIndex + NODE_OFFSET_NEXT, true);
|
||||
}
|
||||
|
||||
get firstNodeIndex(): number {
|
||||
return this.index + 1;
|
||||
}
|
||||
|
||||
private get data(): number {
|
||||
return this.view.getUint32(this._byteIndex + NODE_OFFSET_DATA, true);
|
||||
}
|
||||
@@ -200,7 +204,7 @@ export class RemoteNodeList extends Array<RemoteNode> implements NodeArray<Remot
|
||||
return this.getOrCreateChildAtNodeIndex(next) as RemoteNode;
|
||||
}
|
||||
|
||||
private getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {
|
||||
getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {
|
||||
let child = this.sourceFile.nodes[index];
|
||||
if (!child) {
|
||||
const kind = this.view.getUint32(this.sourceFile._offsetNodes + index * NODE_LEN + NODE_OFFSET_KIND, true);
|
||||
@@ -271,6 +275,36 @@ export class RemoteNode extends RemoteNodeBase implements Node {
|
||||
}
|
||||
}
|
||||
|
||||
*childrenIter<TNext = void>(): Generator<Node, TNext | undefined, TNext> {
|
||||
if (this.hasChildren()) {
|
||||
let next = this.index + 1;
|
||||
do {
|
||||
const child = this.getOrCreateChildAtNodeIndex(next);
|
||||
if (child instanceof RemoteNodeList) {
|
||||
if (child.length) {
|
||||
let listNext = child.firstNodeIndex;
|
||||
while (listNext) {
|
||||
const node = child.getOrCreateChildAtNodeIndex(listNext) as RemoteNode;
|
||||
listNext = node.next;
|
||||
const result = yield node;
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (child.kind !== SyntaxKind.JSDoc) {
|
||||
const result = yield child;
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
next = child.next;
|
||||
}
|
||||
while (next);
|
||||
}
|
||||
}
|
||||
|
||||
get jsDoc(): readonly Node[] | undefined {
|
||||
if (!this.hasChildren()) {
|
||||
return undefined;
|
||||
|
||||
@@ -90,6 +90,7 @@ export interface Node extends ReadonlyTextRange {
|
||||
readonly flags: NodeFlags;
|
||||
readonly parent: Node;
|
||||
readonly jsDoc?: readonly Node[] | undefined;
|
||||
childrenIter<TNext = void>(): Generator<Node, TNext | undefined, TNext>;
|
||||
forEachChild<T>(visitor: (node: Node) => T, visitArray?: (nodes: NodeArray<Node>) => T): T | undefined;
|
||||
getSourceFile(): SourceFile;
|
||||
getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -59,6 +59,43 @@ function forEachChildOfJSDocParameterOrPropertyTag<T>(data: any, cbNode: (node:
|
||||
|
||||
export { forEachChildOfJSDocParameterOrPropertyTag as forEachChildOfJSDocParameterTag, forEachChildOfJSDocParameterOrPropertyTag as forEachChildOfJSDocPropertyTag };
|
||||
|
||||
// ── yieldEachChild implementations ──
|
||||
|
||||
function* yieldEachChildOfJSDocParameterOrPropertyTag<T>(data: any): Generator<Node, T | undefined, T> {
|
||||
if (data.tagName) {
|
||||
const res = yield data.tagName;
|
||||
if (res) return res;
|
||||
}
|
||||
if (data.isNameFirst) {
|
||||
if (data.name) {
|
||||
const res = yield data.name;
|
||||
if (res) return res;
|
||||
}
|
||||
if (data.typeExpression) {
|
||||
const res = yield data.typeExpression;
|
||||
if (res) return res;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (data.typeExpression) {
|
||||
const res = yield data.typeExpression;
|
||||
if (res) return res;
|
||||
}
|
||||
if (data.name) {
|
||||
const res = yield data.name;
|
||||
if (res) return res;
|
||||
}
|
||||
}
|
||||
if (data.comment) {
|
||||
for (const node of data.comment) {
|
||||
const res = yield node;
|
||||
if (res) return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { yieldEachChildOfJSDocParameterOrPropertyTag as yieldEachChildOfJSDocParameterTag, yieldEachChildOfJSDocParameterOrPropertyTag as yieldEachChildOfJSDocPropertyTag };
|
||||
|
||||
// ── visitEachChild implementations ──
|
||||
|
||||
function visitEachChildOfJSDocParameterOrPropertyTag(node: JSDocParameterOrPropertyTag, visitor: Visitor): JSDocParameterOrPropertyTag {
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
// Benchmarks Node.childrenIter against Node.forEachChild for local factory nodes
|
||||
// and RemoteNodes fetched through the sync API.
|
||||
//
|
||||
// Run with:
|
||||
// npm run node -- test/sync/ast.bench.ts
|
||||
// npm run node -- test/sync/ast.bench.ts --filter "remote recursive"
|
||||
|
||||
import type {
|
||||
Block,
|
||||
Identifier,
|
||||
Node,
|
||||
} from "@typescript/typescript/unstable/ast";
|
||||
import {
|
||||
isBlock,
|
||||
isIdentifier,
|
||||
} from "@typescript/typescript/unstable/ast";
|
||||
import {
|
||||
createBlock,
|
||||
createExpressionStatement,
|
||||
createIdentifier,
|
||||
createIfStatement,
|
||||
} from "@typescript/typescript/unstable/ast/factory";
|
||||
import { createVirtualFileSystem } from "@typescript/typescript/unstable/fs";
|
||||
import { API } from "@typescript/typescript/unstable/sync";
|
||||
import assert from "node:assert/strict";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs } from "node:util";
|
||||
import { Bench } from "tinybench";
|
||||
|
||||
const treeDepth = 5;
|
||||
const expressionsPerBlock = 6;
|
||||
const earlyTargetName = "earlyTargetIdentifier";
|
||||
const midpointTargetName = "midpointTargetIdentifier";
|
||||
|
||||
const isMain = process.argv[1] === fileURLToPath(import.meta.url);
|
||||
if (isMain) {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
filter: { type: "string" },
|
||||
singleIteration: { type: "boolean", default: false },
|
||||
},
|
||||
});
|
||||
runBenchmarks(values);
|
||||
}
|
||||
|
||||
interface Tree {
|
||||
node: Block;
|
||||
source: string;
|
||||
}
|
||||
|
||||
function createTree(depth = treeDepth, path = "root"): Tree {
|
||||
const statements = [];
|
||||
const sourceStatements: string[] = [];
|
||||
for (let i = 0; i < expressionsPerBlock; i++) {
|
||||
const name = path === "root" && i === 0
|
||||
? earlyTargetName
|
||||
: path === "root_1" && i === 0
|
||||
? midpointTargetName
|
||||
: `value_${path}_${i}`;
|
||||
statements.push(createExpressionStatement(createIdentifier(name)));
|
||||
sourceStatements.push(`${name};`);
|
||||
}
|
||||
|
||||
if (depth > 0) {
|
||||
const thenTree = createTree(depth - 1, `${path}_0`);
|
||||
const elseTree = createTree(depth - 1, `${path}_1`);
|
||||
const conditionName = `condition_${path}`;
|
||||
statements.push(createIfStatement(
|
||||
createIdentifier(conditionName),
|
||||
thenTree.node,
|
||||
elseTree.node,
|
||||
));
|
||||
sourceStatements.push(`if (${conditionName}) ${thenTree.source} else ${elseTree.source}`);
|
||||
}
|
||||
|
||||
return {
|
||||
node: createBlock(statements),
|
||||
source: `{${sourceStatements.join("\n")}}`,
|
||||
};
|
||||
}
|
||||
|
||||
function collectWithForEachChild(node: Node): Node[] {
|
||||
const children: Node[] = [];
|
||||
node.forEachChild(child => {
|
||||
children.push(child);
|
||||
return undefined;
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
function collectWithChildrenIter(node: Node): Node[] {
|
||||
const children: Node[] = [];
|
||||
for (const child of node.childrenIter()) {
|
||||
children.push(child);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
function walkWithForEachChild(node: Node): number {
|
||||
let count = 1;
|
||||
node.forEachChild(child => {
|
||||
count += walkWithForEachChild(child);
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
function walkWithChildrenIter(node: Node): number {
|
||||
let count = 1;
|
||||
for (const child of node.childrenIter()) {
|
||||
count += walkWithChildrenIter(child);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function findWithForEachChild(node: Node, targetName: string): Identifier | undefined {
|
||||
return node.forEachChild(child => {
|
||||
if (isIdentifier(child) && child.text === targetName) return child;
|
||||
return findWithForEachChild(child, targetName);
|
||||
});
|
||||
}
|
||||
|
||||
function findWithChildrenIter(node: Node, targetName: string): Identifier | undefined {
|
||||
for (const child of node.childrenIter()) {
|
||||
if (isIdentifier(child) && child.text === targetName) return child;
|
||||
const result = findWithChildrenIter(child, targetName);
|
||||
if (result) return result;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function runBenchmarks(options?: { filter?: string; singleIteration?: boolean; }): void {
|
||||
const { filter, singleIteration } = options ?? {};
|
||||
const localTree = createTree();
|
||||
const repoRoot = fileURLToPath(new URL("../../../../", import.meta.url).toString());
|
||||
const configPath = "/tsconfig.json";
|
||||
const sourcePath = "/src/bench.ts";
|
||||
using api = new API({
|
||||
cwd: repoRoot,
|
||||
tsserverPath: fileURLToPath(new URL(`../../../../built/local/tsc${process.platform === "win32" ? ".exe" : ""}`, import.meta.url).toString()),
|
||||
fs: createVirtualFileSystem({
|
||||
[configPath]: "{}",
|
||||
[sourcePath]: localTree.source,
|
||||
}),
|
||||
});
|
||||
|
||||
const snapshot = api.createSnapshot({ openProject: configPath });
|
||||
const remoteSourceFile = snapshot.getConfiguredProject(configPath)!.program.getSourceFile(sourcePath)!;
|
||||
assert.ok(remoteSourceFile.statements);
|
||||
const remoteTree = remoteSourceFile.statements[0];
|
||||
assert.ok(isBlock(remoteTree));
|
||||
|
||||
assert.deepStrictEqual(
|
||||
collectWithChildrenIter(localTree.node).map(node => node.kind),
|
||||
collectWithForEachChild(localTree.node).map(node => node.kind),
|
||||
);
|
||||
assert.strictEqual(walkWithChildrenIter(localTree.node), walkWithForEachChild(localTree.node));
|
||||
assert.strictEqual(walkWithChildrenIter(remoteTree), walkWithForEachChild(remoteTree));
|
||||
assert.strictEqual(walkWithChildrenIter(localTree.node), walkWithChildrenIter(remoteTree));
|
||||
for (const targetName of [earlyTargetName, midpointTargetName]) {
|
||||
assert.strictEqual(findWithChildrenIter(localTree.node, targetName)?.text, targetName);
|
||||
assert.strictEqual(findWithForEachChild(remoteTree, targetName)?.text, targetName);
|
||||
}
|
||||
|
||||
const bench = new Bench({
|
||||
name: "AST child traversal",
|
||||
iterations: 30,
|
||||
warmupIterations: 10,
|
||||
...singleIteration ? {
|
||||
iterations: 1,
|
||||
warmup: false,
|
||||
time: 0,
|
||||
} : undefined,
|
||||
});
|
||||
const isAsync = false;
|
||||
|
||||
for (const [nodeType, node] of [["factory", localTree.node], ["remote", remoteTree]] as const) {
|
||||
bench
|
||||
.add(`${nodeType} immediate children - forEachChild`, () => {
|
||||
assert.strictEqual(collectWithForEachChild(node).length, node.statements.length);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} immediate children - childrenIter`, () => {
|
||||
assert.strictEqual(collectWithChildrenIter(node).length, node.statements.length);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive walk - forEachChild`, () => {
|
||||
assert.ok(walkWithForEachChild(node) > 1);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive walk - childrenIter`, () => {
|
||||
assert.ok(walkWithChildrenIter(node) > 1);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive early exit - forEachChild`, () => {
|
||||
assert.strictEqual(findWithForEachChild(node, earlyTargetName)?.text, earlyTargetName);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive early exit - childrenIter`, () => {
|
||||
assert.strictEqual(findWithChildrenIter(node, earlyTargetName)?.text, earlyTargetName);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive midpoint exit - forEachChild`, () => {
|
||||
assert.strictEqual(findWithForEachChild(node, midpointTargetName)?.text, midpointTargetName);
|
||||
}, { async: isAsync })
|
||||
.add(`${nodeType} recursive midpoint exit - childrenIter`, () => {
|
||||
assert.strictEqual(findWithChildrenIter(node, midpointTargetName)?.text, midpointTargetName);
|
||||
}, { async: isAsync });
|
||||
}
|
||||
|
||||
if (filter) {
|
||||
const pattern = filter.toLowerCase();
|
||||
for (const task of [...bench.tasks]) {
|
||||
if (!task.name.toLowerCase().includes(pattern)) {
|
||||
bench.remove(task.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bench.runSync();
|
||||
for (const task of bench.tasks) {
|
||||
if (task.result && "error" in task.result) throw task.result.error;
|
||||
}
|
||||
console.table(bench.table());
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
createExpressionStatement,
|
||||
createIdentifier,
|
||||
createIfStatement,
|
||||
createMissingDeclaration,
|
||||
createNodeArray,
|
||||
createNumericLiteral,
|
||||
createSourceFile,
|
||||
@@ -44,13 +45,18 @@ import {
|
||||
visitNodes,
|
||||
} from "@typescript/typescript/unstable/ast/visitor";
|
||||
import { createVirtualFileSystem } from "@typescript/typescript/unstable/fs";
|
||||
import { API } from "@typescript/typescript/unstable/sync";
|
||||
import {
|
||||
API,
|
||||
Checker,
|
||||
TypeFlags,
|
||||
} from "@typescript/typescript/unstable/sync";
|
||||
import assert from "node:assert";
|
||||
import {
|
||||
describe,
|
||||
test,
|
||||
} from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runBenchmarks } from "./ast.bench.ts";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -65,6 +71,17 @@ function collectKinds(node: Node): SyntaxKind[] {
|
||||
return kinds;
|
||||
}
|
||||
|
||||
describe("NodeObject + childrenIter", () => {
|
||||
test("skips absent optional child lists", () => {
|
||||
const node = createMissingDeclaration();
|
||||
assert.deepStrictEqual([...node.childrenIter()], []);
|
||||
});
|
||||
});
|
||||
|
||||
test("Benchmarks", async () => {
|
||||
await runBenchmarks({ singleIteration: true });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// cloneNode
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -575,10 +592,14 @@ function spawnAPI(files: Record<string, string> = {
|
||||
});
|
||||
}
|
||||
|
||||
function getRemoteSourceFile(api: API, configPath: string, filePath: string) {
|
||||
function getRemoteSourceFileAndChecker(api: API, configPath: string, filePath: string) {
|
||||
const snapshot = api.createSnapshot({ openProject: configPath });
|
||||
const project = snapshot.getConfiguredProject(configPath)!;
|
||||
return project.program.getSourceFile(filePath)!;
|
||||
return [project.program.getSourceFile(filePath)!, project.checker] as const;
|
||||
}
|
||||
|
||||
function getRemoteSourceFile(api: API, configPath: string, filePath: string) {
|
||||
return getRemoteSourceFileAndChecker(api, configPath, filePath)[0];
|
||||
}
|
||||
|
||||
describe("RemoteNode + cloneNode", () => {
|
||||
@@ -1051,6 +1072,17 @@ describe("RemoteNode + child/token getters", () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function withFirstStatementAsync(source: string, fn: (stmt: Node, sf: SourceFile, api: API<false>, checker: Checker) => Promise<void>) {
|
||||
const api = spawnAPI({ "/tsconfig.json": "{}", "/src/children.ts": source });
|
||||
try {
|
||||
const [sf, checker] = getRemoteSourceFileAndChecker(api, "/tsconfig.json", "/src/children.ts");
|
||||
await fn(sf.statements[0], sf, api, checker);
|
||||
}
|
||||
finally {
|
||||
api.close();
|
||||
}
|
||||
}
|
||||
|
||||
function findFirstOfKind(node: Node, kind: SyntaxKind): Node | undefined {
|
||||
let found: Node | undefined;
|
||||
const walk = (n: Node): undefined => {
|
||||
@@ -1065,6 +1097,54 @@ describe("RemoteNode + child/token getters", () => {
|
||||
return found;
|
||||
}
|
||||
|
||||
test("childrenIter generates all node children and supports early return value passthru", () => {
|
||||
withFirstStatement("if (x) {}", stmt => {
|
||||
const texts: string[] = [];
|
||||
for (const c of stmt.childrenIter()) {
|
||||
texts.push(c.getText());
|
||||
}
|
||||
assert.deepStrictEqual(texts, ["x", "{}"]);
|
||||
const cGen = stmt.childrenIter<Node | undefined>();
|
||||
let state = cGen.next();
|
||||
while (!state.done) {
|
||||
const n = state.value;
|
||||
let foundX: Node | undefined;
|
||||
if (n.getText() === "x") {
|
||||
foundX = n;
|
||||
}
|
||||
state = cGen.next(foundX);
|
||||
}
|
||||
assert.strictEqual(
|
||||
state.value,
|
||||
stmt.forEachChild(n => {
|
||||
return n;
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("childrenIter works with async generators", async () => {
|
||||
await withFirstStatementAsync("class X { p: any }", async (stmt, sf, api, checker) => {
|
||||
async function* visitNodeForFirstAnyChild(node: Node): AsyncGenerator<Node | undefined, Node | undefined, Node | undefined> {
|
||||
for (const n of node.childrenIter()) {
|
||||
const t = await checker.getTypeAtLocation(n);
|
||||
if (t.flags & TypeFlags.Any) return n;
|
||||
const res = yield* visitNodeForFirstAnyChild(n);
|
||||
if (res) return res;
|
||||
}
|
||||
}
|
||||
const res = (await visitNodeForFirstAnyChild(stmt).next()).value;
|
||||
assert.strictEqual(res!.getText(), "p: any");
|
||||
});
|
||||
});
|
||||
|
||||
test("childrenIter skips empty NodeArrays", () => {
|
||||
withFirstStatement("function f() {}", stmt => {
|
||||
const body = findFirstOfKind(stmt, SyntaxKind.Block)!;
|
||||
assert.deepStrictEqual([...body.childrenIter()], []);
|
||||
});
|
||||
});
|
||||
|
||||
test("getChildren materializes the punctuation/keyword tokens the AST omits", () => {
|
||||
withFirstStatement("if (x) {}", stmt => {
|
||||
const texts = stmt.getChildren().map(c => c.getText());
|
||||
|
||||
@@ -1544,6 +1544,10 @@ function emitRemoteNodeList(w: CodeWriter) {
|
||||
w.write(` return this.view.getUint32(this._byteIndex + NODE_OFFSET_NEXT, true);`);
|
||||
w.write(` }`);
|
||||
w.write(``);
|
||||
w.write(` get firstNodeIndex(): number {`);
|
||||
w.write(` return this.index + 1;`);
|
||||
w.write(` }`);
|
||||
w.write(``);
|
||||
w.write(` private get data(): number {`);
|
||||
w.write(` return this.view.getUint32(this._byteIndex + NODE_OFFSET_DATA, true);`);
|
||||
w.write(` }`);
|
||||
@@ -1635,7 +1639,7 @@ function emitRemoteNodeList(w: CodeWriter) {
|
||||
w.write(` return this.getOrCreateChildAtNodeIndex(next) as RemoteNode;`);
|
||||
w.write(` }`);
|
||||
w.write(``);
|
||||
w.write(` private getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {`);
|
||||
w.write(` getOrCreateChildAtNodeIndex(index: number): RemoteNode | RemoteNodeList {`);
|
||||
w.write(` let child = this.sourceFile.nodes[index];`);
|
||||
w.write(` if (!child) {`);
|
||||
w.write(` const kind = this.view.getUint32(this.sourceFile._offsetNodes + index * NODE_LEN + NODE_OFFSET_KIND, true);`);
|
||||
@@ -1708,6 +1712,36 @@ function emitRemoteNodeClassOpen(w: CodeWriter) {
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(``);
|
||||
w.write(` *childrenIter<TNext = void>(): Generator<Node, TNext | undefined, TNext> {`);
|
||||
w.write(` if (this.hasChildren()) {`);
|
||||
w.write(` let next = this.index + 1;`);
|
||||
w.write(` do {`);
|
||||
w.write(` const child = this.getOrCreateChildAtNodeIndex(next);`);
|
||||
w.write(` if (child instanceof RemoteNodeList) {`);
|
||||
w.write(` if (child.length) {`);
|
||||
w.write(` let listNext = child.firstNodeIndex;`);
|
||||
w.write(` while (listNext) {`);
|
||||
w.write(` const node = child.getOrCreateChildAtNodeIndex(listNext) as RemoteNode;`);
|
||||
w.write(` listNext = node.next;`);
|
||||
w.write(` const result = yield node;`);
|
||||
w.write(` if (result) {`);
|
||||
w.write(` return result;`);
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(` else if (child.kind !== SyntaxKind.JSDoc) {`);
|
||||
w.write(` const result = yield child;`);
|
||||
w.write(` if (result) {`);
|
||||
w.write(` return result;`);
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(` next = child.next;`);
|
||||
w.write(` }`);
|
||||
w.write(` while (next);`);
|
||||
w.write(` }`);
|
||||
w.write(` }`);
|
||||
w.write(``);
|
||||
w.write(` get jsDoc(): readonly Node[] | undefined {`);
|
||||
w.write(` if (!this.hasChildren()) {`);
|
||||
w.write(` return undefined;`);
|
||||
|
||||
@@ -679,6 +679,7 @@ function generateFactory(): string {
|
||||
if (members.filter(m => m.isChild()).length > 0) {
|
||||
for (const kind of node.kindTypes()) {
|
||||
handWrittenForEachChildImports.push(`forEachChildOf${kind.name}`);
|
||||
handWrittenForEachChildImports.push(`yieldEachChildOf${kind.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -721,6 +722,11 @@ function generateFactory(): string {
|
||||
out.push(` return fn ? fn(this._data, visitor, visitArray) : undefined;`);
|
||||
out.push(` }`);
|
||||
out.push(``);
|
||||
out.push(` childrenIter<TNext = void>(): Generator<Node, TNext | undefined, TNext> {`);
|
||||
out.push(` const fn = yieldEachChildTable[this.kind];`);
|
||||
out.push(` return fn ? fn(this._data) : emptyChildrenIter();`);
|
||||
out.push(` }`);
|
||||
out.push(``);
|
||||
out.push(` getSourceFile(): SourceFile {`);
|
||||
out.push(` let node: Node = this as unknown as Node;`);
|
||||
out.push(` while (node.parent) node = node.parent;`);
|
||||
@@ -901,6 +907,75 @@ function generateFactory(): string {
|
||||
out.push(`};`);
|
||||
out.push(``);
|
||||
|
||||
// ── yieldEachChildTable ──
|
||||
out.push(`type YieldEachChildFunction = <T>(data: any) => Generator<Node, T | undefined, T>;`);
|
||||
out.push(``);
|
||||
out.push(`function* emptyChildrenIter<T>(): Generator<Node, T | undefined, T> {`);
|
||||
out.push(` return undefined;`);
|
||||
out.push(`}`);
|
||||
out.push(``);
|
||||
out.push(`const yieldEachChildTable: Record<number, YieldEachChildFunction> = {`);
|
||||
// Schema nodes
|
||||
for (const node of api.nodes()) {
|
||||
if (node.handWritten) continue;
|
||||
if (isVariantNode(node)) continue;
|
||||
const members = tsMembers(node);
|
||||
const childMembers = members.filter(m => m.isChild());
|
||||
if (childMembers.length === 0) continue;
|
||||
const syntaxKinds = node.allKinds();
|
||||
if (node.handWrittenVisitor) {
|
||||
for (const sk of syntaxKinds) {
|
||||
out.push(` [${sk.formatTypeScript()}]: yieldEachChildOf${node.name},`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const visits = childMembers.map(m => {
|
||||
const propName = api.uncapitalize(m.name);
|
||||
const listKind = m.listKind;
|
||||
if (listKind) {
|
||||
const visit = `for (const n of data.${propName}) { const res = yield n; if (res) return res; }`;
|
||||
return m.optional ? `if (data.${propName}) { ${visit} }` : visit;
|
||||
}
|
||||
return `if (data.${propName}) { const res = yield data.${propName}; if (res) return res; }`;
|
||||
});
|
||||
const body = visits.join("\n ");
|
||||
for (const sk of syntaxKinds) {
|
||||
out.push(` [${sk.formatTypeScript()}]: function* (data) {`);
|
||||
out.push(` ${body}`);
|
||||
out.push(` },`);
|
||||
}
|
||||
}
|
||||
// Variant nodes
|
||||
for (const v of tsVariants) {
|
||||
if (!v.members) continue;
|
||||
const childMembers = v.members.filter(m => m.type.baseKind() === "node" || m.type.baseKind() === "list");
|
||||
if (childMembers.length === 0) continue;
|
||||
if (v.handWrittenVisitor) {
|
||||
out.push(` [SyntaxKind.${v.syntaxKind}]: yieldEachChildOf${v.tsName},`);
|
||||
continue;
|
||||
}
|
||||
const visits = childMembers.map(m => {
|
||||
const propName = api.uncapitalize(m.name);
|
||||
const listKind = m.listKind;
|
||||
if (listKind) {
|
||||
const visit = `for (const n of data.${propName}) { const res = yield n; if (res) return res; }`;
|
||||
return m.optional ? `if (data.${propName}) { ${visit} }` : visit;
|
||||
}
|
||||
return `if (data.${propName}) { const res = yield data.${propName}; if (res) return res; }`;
|
||||
});
|
||||
const body = visits.join("\n ");
|
||||
out.push(` [SyntaxKind.${v.syntaxKind}]: function* (data) {`);
|
||||
out.push(` ${body}`);
|
||||
out.push(` },`);
|
||||
}
|
||||
// SourceFile is handWritten so we add its yieldEachChild entry manually
|
||||
out.push(` [SyntaxKind.SourceFile]: function* (data) {`);
|
||||
out.push(` for (const n of data.statements) { const res = yield n; if (res) return res; }`);
|
||||
out.push(` const res = yield data.endOfFileToken; if (res) return res;`);
|
||||
out.push(` },`);
|
||||
out.push(`};`);
|
||||
out.push(``);
|
||||
|
||||
// ── visitNode / visitNodes ──
|
||||
out.push(`function visitNode<T>(cbNode: (node: Node) => T, node: Node | undefined): T | undefined {`);
|
||||
out.push(` return node ? cbNode(node) : undefined;`);
|
||||
|
||||
Reference in new issue
Block a user