fix(javascript): preserve repeated native resolver calls (#7824)

This commit is contained in:
morluto authored and GitHub committed 2026-10-29 01:29:48 +08:00
1 parent 477f63ae21
commit aba158506b
3 files changed
+97 -1

No files matched your search

@@ -30,6 +30,7 @@ import {
semanticPropertyPathKeyMatches,
} from "./javascriptSemanticPropertyPaths.js";
import { createSemanticReturnedReferences } from "./javascriptSemanticReturnedReferences.js";
import { semanticRequireLoaderAt } from "./javascriptSemanticRequireOrigin.js";
import {
semanticCopiedReferencePath,
semanticReferenceIterationUnknown,
@@ -772,7 +773,45 @@ export function* indexJavaScriptAmbientWritesSteps(
retainSemanticAmbientEffect(state, "write", resolved, node);
}
}
for (const { node, target, browserReceiver, projection } of escapes) {
// Settle other ambient effects first. A syntactic resolver call is safe only
// if no write or opaque escape can replace the native member anywhere in
// this analysis; otherwise retain its receiver escape conservatively.
const resolverReceivers: typeof escapes = [];
for (const effect of escapes) {
const { node, target } = effect;
const callee =
t.isCallExpression(node) || t.isOptionalCallExpression(node)
? unwrapJavaScriptExpression(node.callee).node
: undefined;
if (
(t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
callee.object === target &&
t.isIdentifier(target, { name: "require" }) &&
isUnshadowedGlobal(target, state, "require") &&
semanticStaticPropertyKey(callee.property, callee.computed) === "resolve"
) {
resolverReceivers.push(effect);
continue;
}
retainEscapeEffect(effect);
}
for (const effect of resolverReceivers) {
const { node } = effect;
if (
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
!state.ambientWrites.covers(["global", "require", "resolve"]) &&
!state.ambientEscapes.covers(["global", "require", "resolve"]) &&
semanticRequireLoaderAt(node, state, new Set()) === "commonjs-loader"
)
continue;
retainEscapeEffect(effect);
}
function retainEscapeEffect({
node,
target,
browserReceiver,
projection,
}: (typeof escapes)[number]): void {
const resolved = ambientWrites(target, node, true, projection);
if (
browserReceiver &&
@@ -70,6 +70,30 @@ it.each([
);
const imported = "import {createRequire as make} from 'node:module';";
it("retains consecutive native resolver calls while keeping replaced or escaped loaders uncertain", () => {
const references = (source: string) =>
analyzeJavaScriptStaticSource(source).references.map(
({ operation, specifier }) => ({ operation, specifier }),
);
expect(
references('require.resolve("./first.js");require.resolve("./second.js");'),
).toEqual([
{ operation: "resolve", specifier: "./first.js" },
{ operation: "resolve", specifier: "./second.js" },
]);
for (const prefix of [
"require.resolve=custom;",
"opaque(require);",
"const box={r:require};box.r.resolve=custom;",
'Object.defineProperty(require,"resolve",{value:custom});',
])
expect(
references(`${prefix}require.resolve("./second.js");`).at(-1),
).toEqual({
operation: "load",
specifier: null,
});
});
it.each([
[
"proven factory",