fix(lint): don't recurse infinitely for large ASTs (#28265)

We previously failed to lint `./cli/tsc/00_typescript.js` with plugins,
because every "next" node would cause a new stack frame to be added.
This commit is contained in:
Luca Casonato 2025-02-24 02:51:30 -08:00 committed by GitHub
parent f373a20a6f
commit 55dc6f4b93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1150,54 +1150,52 @@ export function runPluginsForFile(fileName, serializedAst) {
* @param {CancellationToken} cancellationToken * @param {CancellationToken} cancellationToken
*/ */
function traverse(ctx, visitors, idx, cancellationToken) { function traverse(ctx, visitors, idx, cancellationToken) {
if (idx === AST_IDX_INVALID) return; while (idx !== AST_IDX_INVALID) {
if (cancellationToken.isCancellationRequested()) return; if (cancellationToken.isCancellationRequested()) return;
const { buf } = ctx; const { buf } = ctx;
const nodeType = readType(ctx.buf, idx); const nodeType = readType(ctx.buf, idx);
/** @type {VisitorFn[] | null} */ /** @type {VisitorFn[] | null} */
let exits = null; let exits = null;
// Only visit if it's an actual node // Only visit if it's an actual node
if (nodeType !== AST_GROUP_TYPE) { if (nodeType !== AST_GROUP_TYPE) {
// Loop over visitors and check if any selector matches // Loop over visitors and check if any selector matches
for (let i = 0; i < visitors.length; i++) { for (let i = 0; i < visitors.length; i++) {
const v = visitors[i]; const v = visitors[i];
if (v.matcher(ctx.matcher, idx)) { if (v.matcher(ctx.matcher, idx)) {
if (v.info.exit !== NOOP) { if (v.info.exit !== NOOP) {
if (exits === null) { if (exits === null) {
exits = [v.info.exit]; exits = [v.info.exit];
} else { } else {
exits.push(v.info.exit); exits.push(v.info.exit);
}
}
if (v.info.enter !== NOOP) {
const node = /** @type {*} */ (getNode(ctx, idx));
v.info.enter(node);
} }
} }
}
}
if (v.info.enter !== NOOP) { try {
const childIdx = readChild(buf, idx);
if (childIdx > AST_IDX_INVALID) {
traverse(ctx, visitors, childIdx, cancellationToken);
}
} finally {
if (exits !== null) {
for (let i = 0; i < exits.length; i++) {
const node = /** @type {*} */ (getNode(ctx, idx)); const node = /** @type {*} */ (getNode(ctx, idx));
v.info.enter(node); exits[i](node);
} }
} }
} }
}
try { idx = readNext(buf, idx);
const childIdx = readChild(buf, idx);
if (childIdx > AST_IDX_INVALID) {
traverse(ctx, visitors, childIdx, cancellationToken);
}
} finally {
if (exits !== null) {
for (let i = 0; i < exits.length; i++) {
const node = /** @type {*} */ (getNode(ctx, idx));
exits[i](node);
}
}
}
const nextIdx = readNext(buf, idx);
if (nextIdx > AST_IDX_INVALID) {
traverse(ctx, visitors, nextIdx, cancellationToken);
} }
} }