fix(javascript): preserve repeated native resolver calls (#7824)
This commit is contained in:
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",
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { expect } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
|
||||
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
|
||||
|
||||
cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
"retains consecutive native resolver relationships through %s",
|
||||
async (surface, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-repeat-resolve-");
|
||||
await writeFixtureFiles(root, {
|
||||
"main.cjs":
|
||||
'require.resolve("./first.js");require.resolve("./second.js");',
|
||||
"first.js": "module.exports=1;",
|
||||
"second.js": "module.exports=2;",
|
||||
});
|
||||
const result = await analyzeJavaScriptDirectory(surface, cli, root);
|
||||
const edges = result.graph.edges.filter(
|
||||
(edge) => edge.properties.operation === "resolve",
|
||||
);
|
||||
expect(
|
||||
edges.map((edge) => [
|
||||
edge.properties.specifier,
|
||||
edge.properties.resolved_path,
|
||||
]),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
["./first.js", "first.js"],
|
||||
["./second.js", "second.js"],
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user