refactor(javascript): reuse canonical AST traversal
Remove the separate Vite walker and raw visitor-key require scan. Keep subtree selection at the shared traversal owner. All 103 focused builtin/Vite tests and the complete static and architecture checks pass.
This commit is contained in:
parent
b58adafa4f
commit
a769915f6e
|
|
@ -1,4 +1,5 @@
|
|||
import * as t from "@babel/types";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
|
||||
import { BUILTIN_RESOURCE_METHODS } from "./javascriptBuiltinResourceMethods.js";
|
||||
import type { JavaScriptModuleOrigin } from "./javascriptSemanticIr.js";
|
||||
|
|
@ -61,109 +62,105 @@ const writesModuleRequire = (
|
|||
binding.initializers.every(({ node: init }) => isModuleObject(init, next))
|
||||
);
|
||||
};
|
||||
const pending: t.Node[] = [state.program];
|
||||
while (pending.length > 0) {
|
||||
const node = pending.pop();
|
||||
if (node === undefined) break;
|
||||
const target = t.isAssignmentExpression(node)
|
||||
? node.left
|
||||
: t.isUpdateExpression(node) ||
|
||||
t.isUnaryExpression(node, { operator: "delete" })
|
||||
? node.argument
|
||||
: undefined;
|
||||
if (target !== undefined) {
|
||||
const members: string[] = [];
|
||||
let root: t.Node = target;
|
||||
while (t.isMemberExpression(root) || t.isOptionalMemberExpression(root)) {
|
||||
const key = semanticStaticPropertyKey(root.property, root.computed);
|
||||
if (key === null) break;
|
||||
members.unshift(key);
|
||||
root = root.object;
|
||||
}
|
||||
if (
|
||||
members.length === 1 &&
|
||||
members[0] === "require" &&
|
||||
isModuleObject(root)
|
||||
)
|
||||
writes.push(node);
|
||||
else if (
|
||||
members.length === 0 &&
|
||||
(t.isMemberExpression(target) ||
|
||||
t.isOptionalMemberExpression(target)) &&
|
||||
target.computed &&
|
||||
semanticStaticPropertyKey(target.property, true) === null &&
|
||||
isModuleObject(target.object)
|
||||
)
|
||||
// An unknown computed key may be "require"; retain uncertainty rather
|
||||
// than treating this direct delegate write as a proven unrelated slot.
|
||||
writes.push(node);
|
||||
}
|
||||
if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
|
||||
const invocation = interop.intrinsicInvocation(node);
|
||||
const owner =
|
||||
invocation?.path?.length === 3 ? invocation.path[1] : undefined;
|
||||
const method = invocation?.path?.at(-1);
|
||||
const args = invocation?.arguments;
|
||||
if (
|
||||
(owner === "Object" || owner === "Reflect") &&
|
||||
method === "defineProperty" &&
|
||||
isModuleObject(args?.[0] ?? t.nullLiteral())
|
||||
) {
|
||||
const key = interop.staticString(args?.[1]);
|
||||
if (key === "require" || key === undefined) writes.push(node);
|
||||
} else if (
|
||||
owner === "Object" &&
|
||||
method === "defineProperties" &&
|
||||
isModuleObject(args?.[0] ?? t.nullLiteral())
|
||||
) {
|
||||
const descriptors = args?.[1];
|
||||
traverseJavaScriptAst(state.program, {
|
||||
enter: (node) => {
|
||||
const target = t.isAssignmentExpression(node)
|
||||
? node.left
|
||||
: t.isUpdateExpression(node) ||
|
||||
t.isUnaryExpression(node, { operator: "delete" })
|
||||
? node.argument
|
||||
: undefined;
|
||||
if (target !== undefined) {
|
||||
const members: string[] = [];
|
||||
let root: t.Node = target;
|
||||
while (
|
||||
t.isMemberExpression(root) ||
|
||||
t.isOptionalMemberExpression(root)
|
||||
) {
|
||||
const key = semanticStaticPropertyKey(root.property, root.computed);
|
||||
if (key === null) break;
|
||||
members.unshift(key);
|
||||
root = root.object;
|
||||
}
|
||||
if (
|
||||
!t.isObjectExpression(descriptors) ||
|
||||
descriptors.properties.some(
|
||||
(property) =>
|
||||
t.isSpreadElement(property) ||
|
||||
(t.isObjectProperty(property) &&
|
||||
semanticStaticPropertyKey(property.key, property.computed) ===
|
||||
"require"),
|
||||
)
|
||||
members.length === 1 &&
|
||||
members[0] === "require" &&
|
||||
isModuleObject(root)
|
||||
)
|
||||
writes.push(node);
|
||||
} else if (
|
||||
owner === "Object" &&
|
||||
method === "assign" &&
|
||||
args?.some((arg) => isModuleObject(arg))
|
||||
) {
|
||||
const target = args?.[0];
|
||||
if (target !== undefined && isModuleObject(target)) {
|
||||
const sourceHasRequire = args
|
||||
?.slice(1)
|
||||
.some(
|
||||
(arg) =>
|
||||
t.isObjectExpression(arg) &&
|
||||
arg.properties.some(
|
||||
(property) =>
|
||||
t.isObjectProperty(property) &&
|
||||
semanticStaticPropertyKey(
|
||||
property.key,
|
||||
property.computed,
|
||||
) === "require",
|
||||
),
|
||||
);
|
||||
else if (
|
||||
members.length === 0 &&
|
||||
(t.isMemberExpression(target) ||
|
||||
t.isOptionalMemberExpression(target)) &&
|
||||
target.computed &&
|
||||
semanticStaticPropertyKey(target.property, true) === null &&
|
||||
isModuleObject(target.object)
|
||||
)
|
||||
// An unknown computed key may be "require"; retain uncertainty rather
|
||||
// than treating this direct delegate write as a proven unrelated slot.
|
||||
writes.push(node);
|
||||
}
|
||||
if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
|
||||
const invocation = interop.intrinsicInvocation(node);
|
||||
const owner =
|
||||
invocation?.path?.length === 3 ? invocation.path[1] : undefined;
|
||||
const method = invocation?.path?.at(-1);
|
||||
const args = invocation?.arguments;
|
||||
if (
|
||||
(owner === "Object" || owner === "Reflect") &&
|
||||
method === "defineProperty" &&
|
||||
isModuleObject(args?.[0] ?? t.nullLiteral())
|
||||
) {
|
||||
const key = interop.staticString(args?.[1]);
|
||||
if (key === "require" || key === undefined) writes.push(node);
|
||||
} else if (
|
||||
owner === "Object" &&
|
||||
method === "defineProperties" &&
|
||||
isModuleObject(args?.[0] ?? t.nullLiteral())
|
||||
) {
|
||||
const descriptors = args?.[1];
|
||||
if (
|
||||
sourceHasRequire ||
|
||||
args?.slice(1).some((arg) => !t.isObjectExpression(arg))
|
||||
!t.isObjectExpression(descriptors) ||
|
||||
descriptors.properties.some(
|
||||
(property) =>
|
||||
t.isSpreadElement(property) ||
|
||||
(t.isObjectProperty(property) &&
|
||||
semanticStaticPropertyKey(property.key, property.computed) ===
|
||||
"require"),
|
||||
)
|
||||
)
|
||||
writes.push(node);
|
||||
} else if (
|
||||
owner === "Object" &&
|
||||
method === "assign" &&
|
||||
args?.some((arg) => isModuleObject(arg))
|
||||
) {
|
||||
const target = args?.[0];
|
||||
if (target !== undefined && isModuleObject(target)) {
|
||||
const sourceHasRequire = args
|
||||
?.slice(1)
|
||||
.some(
|
||||
(arg) =>
|
||||
t.isObjectExpression(arg) &&
|
||||
arg.properties.some(
|
||||
(property) =>
|
||||
t.isObjectProperty(property) &&
|
||||
semanticStaticPropertyKey(
|
||||
property.key,
|
||||
property.computed,
|
||||
) === "require",
|
||||
),
|
||||
);
|
||||
if (
|
||||
sourceHasRequire ||
|
||||
args?.slice(1).some((arg) => !t.isObjectExpression(arg))
|
||||
)
|
||||
writes.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
||||
const value = (node as unknown as Record<string, unknown>)[key];
|
||||
if (Array.isArray(value)) {
|
||||
for (const child of value) if (t.isNode(child)) pending.push(child);
|
||||
} else if (t.isNode(value)) pending.push(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
moduleRequireWrites.set(state, writes);
|
||||
return writes;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as t from "@babel/types";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
|
||||
import { htmlUrlText, stripQueryAndFragment } from "../artifactPathSyntax.js";
|
||||
import { sanitizeEndpointCandidate } from "../browserObservation.js";
|
||||
|
|
@ -302,17 +303,19 @@ export const indexVitePreloadDependencyTable = (
|
|||
program: t.Program,
|
||||
): readonly (string | undefined)[] | null => {
|
||||
const helpers: ViteMapDepsFunction[] = [];
|
||||
visitJavaScriptNodes(program, (node) => {
|
||||
if (t.isFunctionDeclaration(node) && node.id?.name === "__vite__mapDeps")
|
||||
helpers.push(node);
|
||||
else if (
|
||||
t.isVariableDeclarator(node) &&
|
||||
t.isIdentifier(node.id, { name: "__vite__mapDeps" })
|
||||
) {
|
||||
const init = node.init;
|
||||
if (t.isFunctionExpression(init) || t.isArrowFunctionExpression(init))
|
||||
helpers.push(init);
|
||||
}
|
||||
traverseJavaScriptAst(program, {
|
||||
enter: (node) => {
|
||||
if (t.isFunctionDeclaration(node) && node.id?.name === "__vite__mapDeps")
|
||||
helpers.push(node);
|
||||
else if (
|
||||
t.isVariableDeclarator(node) &&
|
||||
t.isIdentifier(node.id, { name: "__vite__mapDeps" })
|
||||
) {
|
||||
const init = node.init;
|
||||
if (t.isFunctionExpression(init) || t.isArrowFunctionExpression(init))
|
||||
helpers.push(init);
|
||||
}
|
||||
},
|
||||
});
|
||||
if (helpers.length !== 1) return null;
|
||||
const helper = helpers[0];
|
||||
|
|
@ -323,9 +326,9 @@ export const indexVitePreloadDependencyTable = (
|
|||
: mapParameter;
|
||||
if (!t.isIdentifier(mapIdentifier)) return [];
|
||||
const tables: t.ArrayExpression[] = [];
|
||||
visitJavaScriptNodes(
|
||||
helper,
|
||||
(node) => {
|
||||
traverseJavaScriptAst(helper, {
|
||||
skip: (node) => t.isFunction(node),
|
||||
enter: (node) => {
|
||||
if (
|
||||
t.isAssignmentExpression(node) &&
|
||||
node.operator === "=" &&
|
||||
|
|
@ -338,9 +341,7 @@ export const indexVitePreloadDependencyTable = (
|
|||
)
|
||||
tables.push(node.right);
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
});
|
||||
if (tables.length !== 1) return [];
|
||||
const table = tables[0];
|
||||
if (table === undefined) return [];
|
||||
|
|
@ -350,9 +351,9 @@ export const indexVitePreloadDependencyTable = (
|
|||
: stringValue(element),
|
||||
);
|
||||
let escapedOrMutated = false;
|
||||
visitJavaScriptNodes(
|
||||
program,
|
||||
(node, parent) => {
|
||||
traverseJavaScriptAst(program, {
|
||||
skip: (node) => node === helper,
|
||||
enter: (node, parent) => {
|
||||
if (!t.isIdentifier(node, { name: "__vite__mapDeps" })) return;
|
||||
if (
|
||||
(parent === helper &&
|
||||
|
|
@ -370,40 +371,10 @@ export const indexVitePreloadDependencyTable = (
|
|||
return;
|
||||
escapedOrMutated = true;
|
||||
},
|
||||
helper,
|
||||
);
|
||||
});
|
||||
return escapedOrMutated ? dependencies.map(() => undefined) : dependencies;
|
||||
};
|
||||
|
||||
const visitJavaScriptNodes = (
|
||||
root: t.Node,
|
||||
visit: (node: t.Node, parent: t.Node | undefined) => void,
|
||||
skip?: t.Node,
|
||||
skipNestedFunctions = false,
|
||||
): void => {
|
||||
const pending: { readonly node: t.Node; readonly parent?: t.Node }[] = [
|
||||
{ node: root },
|
||||
];
|
||||
while (pending.length > 0) {
|
||||
const current = pending.pop();
|
||||
if (current === undefined) continue;
|
||||
const { node, parent } = current;
|
||||
if (node === skip) continue;
|
||||
visit(node, parent);
|
||||
if (skipNestedFunctions && node !== root && t.isFunction(node)) continue;
|
||||
const keys = t.VISITOR_KEYS[node.type] ?? [];
|
||||
for (const key of keys) {
|
||||
const child = (node as unknown as Record<string, unknown>)[key];
|
||||
if (Array.isArray(child)) {
|
||||
for (let index = child.length - 1; index >= 0; index -= 1) {
|
||||
const item = child[index];
|
||||
if (t.isNode(item)) pending.push({ node: item, parent: node });
|
||||
}
|
||||
} else if (t.isNode(child)) pending.push({ node: child, parent: node });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inspectEndpointCall = (
|
||||
node: JavaScriptCallLike,
|
||||
name: string,
|
||||
|
|
|
|||
Loading…
Reference in New Issue