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:
morluto
2026-10-14 16:00:20 +08:00
parent f2f83934d2
commit eef4a0bfb4
8 changed files with 509 additions and 32 deletions
@@ -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);