fix(javascript): derive loader references from lexical binding ownership

Use shared lexical bindings and recovered bundler identities for require and chunk references. Preserve uncertain writes as unresolved evidence; remove name-only loader aliases and repeated definition scans.

Reviewed-PR: #2989
Co-authored-by: Hussnain Ahmad <ha7886899@gmail.com>
This commit is contained in:
morlutoandHussnain Ahmad committed 2026-10-14 16:10:46 +08:00
1 parent f2f83934d2
commit eef4a0bfb4
8 files changed
+509 -32

No files matched your search

@@ -78,6 +78,7 @@ interface BindPatternInput {
readonly scope: JavaScriptSemanticScopeState;
readonly state: JavaScriptSemanticAnalysisState;
readonly mutable: boolean;
readonly declarationKind?: "var" | "let" | "const" | "using" | "await using";
readonly kind?: JavaScriptSemanticDefinition["kind"];
readonly projection?: readonly (string | number | null)[];
readonly referenceOnly?: boolean;
@@ -101,6 +102,7 @@ interface AddBindingInput {
readonly name: string;
readonly kind: JavaScriptSemanticDefinition["kind"];
readonly mutable: boolean;
readonly declarationKind?: "var" | "let" | "const" | "using" | "await using";
readonly definitionNode: t.Node;
readonly initializer: t.Node | null;
readonly directOrigin?: JavaScriptModuleOrigin;
@@ -976,6 +978,9 @@ const bindInnerDeclaration = (
parent !== null && t.isVariableDeclaration(parent)
? parent.kind !== "const"
: true,
...(parent !== null && t.isVariableDeclaration(parent)
? { declarationKind: parent.kind }
: {}),
});
else if (t.isClassExpression(node) && t.isIdentifier(node.id))
addBinding({
@@ -1258,6 +1263,7 @@ const bindPattern = (input: BindPatternInput): void => {
scope,
state,
mutable,
declarationKind,
kind = "variable",
projection = [],
referenceOnly = false,
@@ -1300,6 +1306,7 @@ const bindPattern = (input: BindPatternInput): void => {
name: pattern.name,
kind,
mutable,
...(declarationKind === undefined ? {} : { declarationKind }),
definitionNode: pattern,
initializer,
projection,
@@ -1402,6 +1409,7 @@ const addBinding = (input: AddBindingInput): void => {
name,
kind,
mutable,
declarationKind,
definitionNode,
initializer,
directOrigin,
@@ -1416,6 +1424,8 @@ const addBinding = (input: AddBindingInput): void => {
state.bindingsById.set(binding.bindingId, binding);
}
binding.mutable ||= mutable;
if (declarationKind !== undefined)
binding.declarationKinds.add(declarationKind);
binding.definitions.push({ kind, location: range(definitionNode) });
if (initializer !== null)
binding.initializers.push({ node: initializer, projection });
@@ -1443,6 +1453,7 @@ const createBinding = (
name,
kind,
mutable,
declarationKinds: new Set(),
memberWrites: [],
escapedPaths: [],
definitions: [],
@@ -47,6 +47,7 @@ describe("parameter references copied into function body bindings", () => {
scopeId: "body",
name: "arg",
kind: "variable",
declarationKinds: new Set(),
mutable: true,
memberWrites: [],
escapedPaths: [],
@@ -6,6 +6,7 @@ import type { JavaScriptModuleOrigin } from "./javascriptSemanticIr.js";
import {
resolveSemanticBindingState,
semanticResolutionBlocked,
semanticStableInitializer,
type JavaScriptSemanticAnalysisState,
} from "./javascriptSemanticState.js";
import {
@@ -22,6 +23,169 @@ export interface JavaScriptRequireOrigin {
readonly call: t.CallExpression;
}
export type JavaScriptRequireLoaderFact =
| "commonjs-loader"
| "factory-loader"
| "create-require"
| "local"
| "unknown";
/** Classify a bare `require(...)` using the shared lexical binding owner. */
export const semanticRequireLoaderAt = (
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
state: JavaScriptSemanticAnalysisState,
factoryBindingIds: ReadonlySet<string>,
): JavaScriptRequireLoaderFact => {
if (!t.isIdentifier(node.callee, { name: "require" })) return "local";
const binding = resolveSemanticBindingState(state, node.callee, "require");
if (binding === undefined)
return semanticResolutionBlocked(state, node.callee, "require")
? "unknown"
: "commonjs-loader";
if (factoryBindingIds.has(binding.bindingId))
return binding.initializers.length === 0 ? "factory-loader" : "unknown";
if (isWrapperRequireInitializerCall(binding, node, state))
return "commonjs-loader";
const scope = state.scopesById.get(binding.scopeId);
if (
scope?.kind === "program" &&
binding.declarationKinds.size > 0 &&
[...binding.declarationKinds].every((kind) => kind === "var") &&
binding.initializers.length === 0
)
return "commonjs-loader";
if (isSemanticCreateRequireBinding(binding, state)) return "create-require";
const initializer = semanticStableInitializer(binding, state)?.node;
if (
(binding.kind === "function" || binding.kind === "class") &&
initializer !== undefined
)
return "local";
if (
binding.declarationKinds.size === 1 &&
binding.declarationKinds.has("const") &&
(t.isFunctionExpression(initializer) ||
t.isArrowFunctionExpression(initializer))
)
return "local";
return "unknown";
};
/** Classify a call through an exact recovered factory-loader binding. */
export const semanticFactoryRequireLoaderAt = (
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
state: JavaScriptSemanticAnalysisState,
bindingIds: ReadonlySet<string>,
): JavaScriptRequireLoaderFact | undefined => {
const callee = unwrapJavaScriptExpression(node.callee).node;
if (t.isIdentifier(callee))
return semanticFactoryLoaderMemberAt(node, state, bindingIds, null);
if (
(t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
semanticStaticPropertyKey(callee.property, callee.computed) === "bind"
)
return semanticFactoryLoaderMemberAt(node, state, bindingIds, "bind");
return undefined;
};
/** Classify an exact loader call by binding identity and expected member. */
export const semanticFactoryLoaderMemberAt = (
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
state: JavaScriptSemanticAnalysisState,
bindingIds: ReadonlySet<string>,
member: string | null,
): JavaScriptRequireLoaderFact | undefined => {
const callee = unwrapJavaScriptExpression(node.callee).node;
let loader: t.Identifier | undefined;
if (member === null) {
if (t.isIdentifier(callee)) loader = callee;
} else if (
(t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
semanticStaticPropertyKey(callee.property, callee.computed) === member
) {
const root = unwrapJavaScriptExpression(callee.object).node;
if (t.isIdentifier(root)) loader = root;
}
if (loader === undefined) return undefined;
const binding = resolveSemanticBindingState(state, loader, loader.name);
if (binding === undefined || !bindingIds.has(binding.bindingId))
return undefined;
return binding.initializers.length === 0 ? "factory-loader" : "unknown";
};
const isSemanticCreateRequireBinding = (
binding: NonNullable<ReturnType<typeof resolveSemanticBindingState>>,
state: JavaScriptSemanticAnalysisState,
): boolean => {
if (binding.initializers.length !== 1) return false;
const initializer = semanticStableInitializer(binding, state)?.node;
if (!t.isCallExpression(initializer)) return false;
const callee = unwrapJavaScriptExpression(initializer.callee).node;
if (t.isIdentifier(callee)) {
const origins = resolveSemanticBindingState(
state,
callee,
callee.name,
)?.directOrigins;
if (origins?.length !== 1) return false;
const [origin] = origins;
return (
origin?.importedPath.at(-1) === "createRequire" &&
["module", "node:module"].includes(origin.specifier)
);
}
const chain = semanticMemberChain(callee);
if (chain === undefined || chain.members.at(-1) !== "createRequire")
return false;
if (!t.isNode(chain.root)) return false;
const root = unwrapJavaScriptExpression(chain.root).node;
if (!t.isIdentifier(root)) return false;
const origins = resolveSemanticBindingState(
state,
root,
root.name,
)?.directOrigins;
if (origins?.length !== 1) return false;
const [origin] = origins;
return (
origin?.importedPath.length === 0 &&
["module", "node:module"].includes(origin.specifier)
);
};
/** The RHS call runs before its assignment replaces Node's wrapper loader. */
const isWrapperRequireInitializerCall = (
binding: NonNullable<ReturnType<typeof resolveSemanticBindingState>>,
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
state: JavaScriptSemanticAnalysisState,
): boolean => {
const scope = state.scopesById.get(binding.scopeId);
if (
scope?.kind !== "program" ||
binding.kind !== "variable" ||
binding.declarationKinds.size !== 1 ||
!binding.declarationKinds.has("var") ||
binding.initializers.length !== 1 ||
binding.initializers[0]?.node !== node
)
return false;
const assignment = state.parentsByNode.get(node);
if (
!t.isAssignmentExpression(assignment) ||
assignment.right !== node ||
state.conditionallySkippedExpressions.has(node)
)
return false;
const statement = state.parentsByNode.get(assignment);
if (!t.isExpressionStatement(statement)) return false;
if (state.parentsByNode.get(statement) !== state.program) return false;
const target = unwrapJavaScriptExpression(assignment.left).node;
return (
t.isIdentifier(target, { name: "require" }) &&
resolveSemanticBindingState(state, target, target.name) === binding
);
};
// Ambient indexing finishes before these derived facts are consumed. Keep only
// replacement sites, rather than rescanning unrelated global member writes.
const globalProcessReplacements = new WeakMap<
@@ -46,6 +46,8 @@ export interface JavaScriptSemanticBindingState {
readonly scopeId: string;
readonly name: string;
kind: JavaScriptSemanticDefinition["kind"];
/** Source declaration form retained internally for CommonJS wrapper bindings. */
declarationKinds: Set<"var" | "let" | "const" | "using" | "await using">;
mutable: boolean;
readonly memberWrites: JavaScriptSemanticMemberEffect[];
readonly escapedPaths: JavaScriptSemanticMemberEffect[];
@@ -5,6 +5,7 @@ import {
resolveSemanticBindingState,
type JavaScriptSemanticAnalysisState,
} from "./javascriptSemanticState.js";
import { semanticFactoryLoaderMemberAt } from "./javascriptSemanticRequireOrigin.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
@@ -46,7 +47,11 @@ export const inspectBundlerRegistration = (
if (runtime === undefined || !t.isArrayExpression(entry)) return;
const chunkIds = entry.elements[0];
const table = entry.elements[1];
const runtimeValue = runtimeMetadata(entry.elements[2]);
const runtimeValue = runtimeMetadata(
entry.elements[2],
bindings,
accumulator,
);
if (!t.isArrayExpression(chunkIds) || !t.isObjectExpression(table)) return;
const recovered = recoverBundlerModules(source, table, accumulator, {
runtime,
@@ -253,7 +258,21 @@ const recoverBundlerModule = (
const fingerprint = fingerprintJavaScriptAst(factory);
const exportsValue = collectJavaScriptExports(factory, bindings);
const requireName = factoryRequireName(factory);
const asyncChunks = collectBundlerAsyncChunkKeys(factory, requireName);
const requireParameter = runtimeFunctionParameters(factory)[2];
const requireBindingId = t.isIdentifier(requireParameter)
? (resolveSemanticBindingState(
bindings,
requireParameter,
requireParameter.name,
)?.bindingId ?? null)
: null;
if (requireBindingId !== null)
accumulator.factoryRequireBindingIds.add(requireBindingId);
const asyncChunks = collectBundlerAsyncChunkKeys(
factory,
requireBindingId,
bindings,
);
if (typeof factory.start === "number" && typeof factory.end === "number") {
const moduleRange = {
start: factory.start,
@@ -297,7 +316,11 @@ type BundlerFunction =
| t.ArrowFunctionExpression
| t.ObjectMethod;
const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
const runtimeMetadata = (
node: t.Node | null | undefined,
bindings: JavaScriptSemanticAnalysisState,
accumulator: AnalysisAccumulator,
): RuntimeMetadata => {
if (!isBundlerFunction(node))
return {
requireName: null,
@@ -308,6 +331,12 @@ const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
};
const parameter = runtimeFunctionParameters(node)[0];
const requireName = t.isIdentifier(parameter) ? parameter.name : null;
const requireBindingId = t.isIdentifier(parameter)
? (resolveSemanticBindingState(bindings, parameter, parameter.name)
?.bindingId ?? null)
: null;
if (requireBindingId !== null)
accumulator.factoryRequireBindingIds.add(requireBindingId);
if (requireName === null)
return {
requireName,
@@ -316,8 +345,18 @@ const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
asyncChunkKeys: [],
unknownAsyncChunkKeys: 0,
};
const entries = collectBundlerCallArgumentValues(node, requireName);
const asyncChunks = collectBundlerAsyncChunkKeys(node, requireName);
const entries = collectBundlerCallArgumentValues(
node,
requireBindingId,
bindings,
null,
);
const asyncChunks = collectBundlerCallArgumentValues(
node,
requireBindingId,
bindings,
"e",
);
return {
requireName,
entryModuleKeys: entries.values,
@@ -329,23 +368,43 @@ const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
const collectBundlerAsyncChunkKeys = (
factory: BundlerFunction,
requireName: string | null,
requireBindingId: string | null,
bindings: JavaScriptSemanticAnalysisState,
): StaticValues =>
requireName === null
requireBindingId === null
? { values: [], unknown: 0 }
: collectBundlerCallArgumentValues(factory, `${requireName}.e`);
: collectBundlerCallArgumentValues(
factory,
requireBindingId,
bindings,
"e",
);
const collectBundlerCallArgumentValues = (
factory: BundlerFunction,
callee: string,
bindingId: string | null,
bindings: JavaScriptSemanticAnalysisState,
member: string | null,
): StaticValues => {
if (bindingId === null) return { values: [], unknown: 0 };
const values: string[] = [];
let unknown = 0;
const bindingIds = new Set([bindingId]);
traverseJavaScriptAst(factory, {
enter: (node) => {
if (!t.isCallExpression(node) && !t.isNewExpression(node))
return undefined;
if (calleeName(node.callee) !== callee) return undefined;
const loader = semanticFactoryLoaderMemberAt(
node,
bindings,
bindingIds,
member,
);
if (loader === undefined) return undefined;
if (loader === "unknown") {
unknown += 1;
return undefined;
}
const value = argumentValue(node.arguments[0]);
if (value === undefined) unknown += 1;
else values.push(value);
@@ -40,6 +40,11 @@ import {
isUnmodifiedGlobalReference,
isUnmodifiedImportMetaUrl,
} from "./javascriptSemanticAmbientWrites.js";
import {
semanticFactoryLoaderMemberAt,
semanticFactoryRequireLoaderAt,
semanticRequireLoaderAt,
} from "./javascriptSemanticRequireOrigin.js";
type JavaScriptCallLike =
| t.CallExpression
@@ -56,16 +61,14 @@ export const inspectCall = (
const { accumulator } = context;
const name = calleeName(node.callee);
const first = stringValue(node.arguments[0]);
const module = context.accumulator.moduleRangeIndex.find(node.start);
const moduleRequireName = module?.requireName ?? null;
const requireCall = requireCallSpecifier(node, name, moduleRequireName);
const requireCall = requireCallSpecifier(node, bindings, context);
if (requireCall !== null)
addReference(context, {
node,
kind: "require",
specifier: requireCall.specifier,
});
const chunkLoad = chunkLoadSpecifier(node, name, moduleRequireName);
const chunkLoad = chunkLoadSpecifier(node, bindings, context);
if (chunkLoad !== null)
addReference(context, {
node,
@@ -224,31 +227,61 @@ const noteMediaElementPreloads = (
const requireCallSpecifier = (
node: JavaScriptCallLike,
name: string,
moduleRequireName: string | null,
bindings: JavaScriptSemanticAnalysisState,
context: FindingContext,
): { readonly specifier: string | undefined } | null => {
if (name === "require" || name.endsWith(".require"))
return { specifier: argumentValue(node.arguments[0]) };
if (name === "__webpack_require__" || name === moduleRequireName)
return { specifier: argumentValue(node.arguments[0]) };
if (
name === "__webpack_require__.bind" ||
(moduleRequireName !== null && name === `${moduleRequireName}.bind`)
)
return { specifier: argumentValue(node.arguments[1]) };
const bareRequire = t.isIdentifier(node.callee, { name: "require" });
if (bareRequire) {
const loader = semanticRequireLoaderAt(
node,
bindings,
context.accumulator.factoryRequireBindingIds,
);
if (loader === "local") return null;
if (loader === "unknown") context.accumulator.unknownFindings += 1;
return {
specifier:
loader === "unknown" ? undefined : argumentValue(node.arguments[0]),
};
}
const factoryLoader = semanticFactoryRequireLoaderAt(
node,
bindings,
context.accumulator.factoryRequireBindingIds,
);
if (factoryLoader === "factory-loader")
return {
specifier: argumentValue(
node.arguments[
t.isMemberExpression(unwrapJavaScriptExpression(node.callee).node) ||
t.isOptionalMemberExpression(
unwrapJavaScriptExpression(node.callee).node,
)
? 1
: 0
],
),
};
if (factoryLoader === "unknown") {
context.accumulator.unknownFindings += 1;
return { specifier: undefined };
}
return null;
};
const chunkLoadSpecifier = (
node: JavaScriptCallLike,
name: string,
moduleRequireName: string | null,
bindings: JavaScriptSemanticAnalysisState,
context: FindingContext,
): { readonly specifier: string | undefined } | null => {
if (
name !== "__webpack_require__.e" &&
(moduleRequireName === null || name !== `${moduleRequireName}.e`)
)
return null;
const loader = semanticFactoryLoaderMemberAt(
node,
bindings,
context.accumulator.factoryRequireBindingIds,
"e",
);
if (loader === undefined) return null;
if (loader === "unknown") return { specifier: undefined };
return { specifier: prefixedArgument("chunk:", node.arguments[0]) };
};
@@ -62,6 +62,210 @@ it("does not treat a same-named shadow as the factory exports parameter", () =>
]);
});
it("does not resolve a parameter named require as a module dependency", () => {
const analysis = analyzeJavaScriptStaticSource(
`function run(require) { return require("./ghost.cjs"); }`,
);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./ghost.cjs" }),
);
expect(analysis.references).toContainEqual(
expect.objectContaining({
kind: "require",
specifier: null,
expression: 'require("./ghost.cjs")',
}),
);
});
it("keeps a top-level wrapper require redeclaration", () => {
const analysis = analyzeJavaScriptStaticSource(
`var require; require("./wrapper.cjs");`,
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ specifier: "./wrapper.cjs" }),
);
});
it("retains the wrapper dependency evaluated inside its replacing assignment", () => {
const analysis = analyzeJavaScriptStaticSource(
`var require; require = require("./before-replacement.cjs");`,
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ specifier: "./before-replacement.cjs" }),
);
});
it.each([
[
"a deferred function assignment",
`var require; function load() { require = require("./maybe.cjs"); } load(); load();`,
],
[
"an assignment repeated by a loop",
`var require; while (condition) { require = require("./maybe.cjs"); }`,
],
])("keeps %s unknown", (_label, source) => {
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./maybe.cjs" }),
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ kind: "require", specifier: null }),
);
});
it.each([
[
"a skipped wrapper write",
`var require; if (false) require = localLoader; require("./ghost.cjs");`,
"./ghost.cjs",
],
[
"a skipped createRequire write",
`import { createRequire } from "node:module"; let require = () => "local"; if (false) require = createRequire(import.meta.url); require("./ghost.cjs");`,
"./ghost.cjs",
],
[
"a class static field write before its computed key in source order",
`var require; class C { static value = (require = () => "local"); [require("./computed-key.cjs")]() {} }`,
"./computed-key.cjs",
],
[
"a loop condition write before a later call",
`var require; while ((require = () => "local", false)) {} require("./after-loop.cjs");`,
"./after-loop.cjs",
],
])("preserves uncertainty for %s", (_label, source, specifier) => {
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier }),
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ kind: "require", specifier: null }),
);
});
it("keeps a proven createRequire binding", () => {
const analysis = analyzeJavaScriptStaticSource(`
import { createRequire as makeRequire } from "node:module";
const require = makeRequire(import.meta.url);
require("./created.cjs");
`);
expect(analysis.references).toEqual(
expect.arrayContaining([
expect.objectContaining({ specifier: "./created.cjs" }),
]),
);
});
it("recognizes only factory loader calls and supported bind references", () => {
const analysis = analyzeJavaScriptStaticSource(`
globalThis.webpackChunkApp.push([[1], {
"factory": function (module, exports, loader) {
loader.resolve("./not-a-dependency.cjs");
loader("./dependency.cjs");
loader.bind(module, "./bound-dependency.cjs");
}
}]);
`);
expect(analysis.references.map(({ specifier }) => specifier)).toEqual(
expect.arrayContaining(["./dependency.cjs", "./bound-dependency.cjs"]),
);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./not-a-dependency.cjs" }),
);
});
it("keeps reassigned factory loaders unknown instead of resolving imports", () => {
const analysis = analyzeJavaScriptStaticSource(`
globalThis.webpackChunkApp.push([[1], {
"factory": function (module, exports, loader) {
loader = userInput;
loader("./uncertain.cjs");
}
}]);
`);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./uncertain.cjs" }),
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ kind: "require", specifier: null }),
);
});
it("keeps a reassigned function named require unknown", () => {
const analysis = analyzeJavaScriptStaticSource(`
function require() { return userInput; }
require = replacement;
require("./uncertain.cjs");
`);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./uncertain.cjs" }),
);
expect(analysis.references).toContainEqual(
expect.objectContaining({ kind: "require", specifier: null }),
);
});
it("does not treat a shadowed webpack loader name as bundler ownership", () => {
const analysis = analyzeJavaScriptStaticSource(`
function local(__webpack_require__) {
__webpack_require__("./ghost.cjs");
__webpack_require__.e("ghost-chunk");
}
`);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "./ghost.cjs" }),
);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "chunk:ghost-chunk" }),
);
});
it("uses recovered factory and runtime loader identities for webpack imports and chunks", () => {
const analysis = analyzeJavaScriptStaticSource(`
globalThis.webpackChunkApp.push([[1], {
"factory": function (module, exports, __webpack_require__) {
__webpack_require__("./factory-dependency.cjs");
__webpack_require__.e("factory-chunk");
}
}, function (__webpack_require__) {
__webpack_require__("factory");
__webpack_require__.e("runtime-chunk");
}]);
`);
expect(analysis.references.map(({ specifier }) => specifier)).toEqual(
expect.arrayContaining([
"./factory-dependency.cjs",
"chunk:factory-chunk",
"chunk:runtime-chunk",
]),
);
expect(analysis.bundler_registrations[0]?.async_chunk_keys).toEqual(
expect.arrayContaining(["factory-chunk", "runtime-chunk"]),
);
});
it("does not attribute a shadowed factory loader parameter to async chunks", () => {
const analysis = analyzeJavaScriptStaticSource(`
globalThis.webpackChunkApp.push([[1], {
"factory": function (module, exports, loader) {
function local(__webpack_require__) {
__webpack_require__.e("ghost-chunk");
}
loader.e("real-chunk");
}
}]);
`);
expect(analysis.bundler_registrations[0]?.async_chunk_keys).toEqual([
"real-chunk",
]);
expect(analysis.references).not.toContainEqual(
expect.objectContaining({ specifier: "chunk:ghost-chunk" }),
);
});
it("does not confuse a shadowed dispatch table with the outer module table", () => {
const analysis = analyzeJavaScriptStaticSource(`(() => {
const modules = { 17(module) { module.exports = require("electron"); } };
@@ -53,6 +53,8 @@ export interface JavaScriptAnalysisAccumulator {
readonly utilityProcesses: LocatedJavaScriptFinding<ElectronUtilityProcessFinding>[];
readonly nativeAddonBindings: LocatedJavaScriptFinding<ElectronNativeAddonBindingFinding>[];
readonly moduleRangeIndex: JavaScriptModuleRangeIndex;
/** Binding identities proven as third-parameter loaders of recovered factories. */
readonly factoryRequireBindingIds: Set<string>;
/** `preload` attributes of audio and video elements, which are not scripts. */
readonly mediaElementPreloads: WeakSet<t.Node>;
readonly seen: Set<string>;
@@ -114,6 +116,7 @@ export const createJavaScriptAnalysisAccumulator = (
utilityProcesses: [],
nativeAddonBindings: [],
moduleRangeIndex: new JavaScriptModuleRangeIndex(sourceLength),
factoryRequireBindingIds: new Set(),
mediaElementPreloads: new WeakSet(),
seen: new Set(),
visitedNodes: 0,