mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 05:16:16 +00:00
fix(javascript): unify loader binding and mutation ownership
Model the CommonJS wrapper binding and query shared lexical identity, mutation, and evaluation timing at each call. Remove duplicate importer interpretation and preserve uncertainty for with scopes, prototypes, repeated writes, and opaque object escapes. Validate generated fixtures against bounded Node oracles; no analyzed target execution is added. Refs: #3982, #4072 Co-authored-by: Geronimo <varungore1@gmail.com>
This commit is contained in:
1 parent
b67baede2e
commit
1b9b725c4c
8 files changed
+1089
-295
No files matched your search
@@ -6,6 +6,7 @@ import type { JavaScriptModuleOrigin } from "./javascriptSemanticIr.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
isUnmodifiedCommonJsWrapperBinding,
|
||||
commonJsWrapperBindingDeclaration,
|
||||
semanticResolutionBlocked,
|
||||
semanticStableInitializer,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
@@ -16,7 +17,10 @@ import {
|
||||
} from "./javascriptAstValues.js";
|
||||
import { semanticPropertyPathKeyMatches } from "./javascriptSemanticPropertyPaths.js";
|
||||
import { stringValue } from "./javascriptStaticAnalysisHelpers.js";
|
||||
import { semanticTopLevelEffectFollowsCapture } from "./javascriptSemanticMutationInitializers.js";
|
||||
import {
|
||||
semanticEffectTiming,
|
||||
semanticTopLevelEffectFollowsCapture,
|
||||
} from "./javascriptSemanticMutationInitializers.js";
|
||||
import { createSemanticModuleInterop } from "./javascriptModuleInterop.js";
|
||||
|
||||
export interface JavaScriptRequireOrigin {
|
||||
@@ -37,17 +41,49 @@ export const semanticRequireLoaderAt = (
|
||||
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")
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
const loader = t.isIdentifier(callee, { name: "require" })
|
||||
? callee
|
||||
: (t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
|
||||
["resolve", "main"].includes(
|
||||
semanticStaticPropertyKey(callee.property, callee.computed) ?? "",
|
||||
) &&
|
||||
t.isIdentifier(callee.object, { name: "require" })
|
||||
? callee.object
|
||||
: undefined;
|
||||
if (loader === undefined) return "local";
|
||||
const binding = resolveSemanticBindingState(state, loader, "require");
|
||||
if (binding === undefined) {
|
||||
if (semanticResolutionBlocked(state, loader, "require")) return "unknown";
|
||||
return (state.ambientBindingWrites.get("require") ?? []).some(
|
||||
(write) => semanticEffectTiming(node, write, state) !== "after",
|
||||
)
|
||||
? "unknown"
|
||||
: "commonjs-loader";
|
||||
}
|
||||
if (factoryBindingIds.has(binding.bindingId))
|
||||
return binding.initializers.length === 0 ? "factory-loader" : "unknown";
|
||||
if (
|
||||
binding.hostOrigin === "commonjs-wrapper" &&
|
||||
[...binding.declarationKinds].every((kind) => kind === "var")
|
||||
) {
|
||||
if (binding.definitions.some(({ kind }) => kind === "function"))
|
||||
return "local";
|
||||
return commonJsWrapperBindingHasPriorWrite(node, binding, state)
|
||||
? "unknown"
|
||||
: "commonjs-loader";
|
||||
}
|
||||
if (isWrapperRequireInitializerCall(binding, node, state))
|
||||
return "commonjs-loader";
|
||||
if (isUnmodifiedCommonJsWrapperBinding(node.callee, "require", state))
|
||||
const wrapperBinding = commonJsWrapperBindingDeclaration(
|
||||
loader,
|
||||
"require",
|
||||
state,
|
||||
);
|
||||
if (
|
||||
wrapperBinding !== undefined &&
|
||||
!commonJsWrapperBindingHasPriorWrite(node, wrapperBinding, state)
|
||||
)
|
||||
return "commonjs-loader";
|
||||
if (isSemanticCreateRequireBinding(binding, state)) return "create-require";
|
||||
const initializer = semanticStableInitializer(binding, state)?.node;
|
||||
@@ -66,6 +102,42 @@ export const semanticRequireLoaderAt = (
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
const commonJsWrapperBindingHasPriorWrite = (
|
||||
site: t.Node,
|
||||
binding: NonNullable<ReturnType<typeof resolveSemanticBindingState>>,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
const writes = state.bindingWrites.get(binding.bindingId) ?? [];
|
||||
const declarationInitializers = binding.initializers
|
||||
.map(({ node }) => node)
|
||||
.filter(
|
||||
(initializer) =>
|
||||
!writes.some((write) => semanticNodeWithin(initializer, write, state)),
|
||||
);
|
||||
return (
|
||||
writes.some(
|
||||
(write) => semanticEffectTiming(site, write, state) !== "after",
|
||||
) ||
|
||||
declarationInitializers.some(
|
||||
(initializer) =>
|
||||
semanticEffectTiming(site, initializer, state) !== "after",
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const semanticNodeWithin = (
|
||||
node: t.Node,
|
||||
ancestor: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
let current: t.Node | undefined = node;
|
||||
while (current !== undefined) {
|
||||
if (current === ancestor) return true;
|
||||
current = state.parentsByNode.get(current);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/** Classify a call through an exact recovered factory-loader binding. */
|
||||
export const semanticFactoryRequireLoaderAt = (
|
||||
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
|
||||
|
||||
Reference in new issue
Block a user