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
*/
function traverse(ctx, visitors, idx, cancellationToken) {
if (idx === AST_IDX_INVALID) return;
if (cancellationToken.isCancellationRequested()) return;
while (idx !== AST_IDX_INVALID) {
if (cancellationToken.isCancellationRequested()) return;
const { buf } = ctx;
const nodeType = readType(ctx.buf, idx);
const { buf } = ctx;
const nodeType = readType(ctx.buf, idx);
/** @type {VisitorFn[] | null} */
let exits = null;
/** @type {VisitorFn[] | null} */
let exits = null;
// Only visit if it's an actual node
if (nodeType !== AST_GROUP_TYPE) {
// Loop over visitors and check if any selector matches
for (let i = 0; i < visitors.length; i++) {
const v = visitors[i];
if (v.matcher(ctx.matcher, idx)) {
if (v.info.exit !== NOOP) {
if (exits === null) {
exits = [v.info.exit];
} else {
exits.push(v.info.exit);
// Only visit if it's an actual node
if (nodeType !== AST_GROUP_TYPE) {
// Loop over visitors and check if any selector matches
for (let i = 0; i < visitors.length; i++) {
const v = visitors[i];
if (v.matcher(ctx.matcher, idx)) {
if (v.info.exit !== NOOP) {
if (exits === null) {
exits = [v.info.exit];
} else {
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));
v.info.enter(node);
exits[i](node);
}
}
}
}
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));
exits[i](node);
}
}
}
const nextIdx = readNext(buf, idx);
if (nextIdx > AST_IDX_INVALID) {
traverse(ctx, visitors, nextIdx, cancellationToken);
idx = readNext(buf, idx);
}
}