mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
refactor(javascript): break the semantic analysis import cycle (#8063)
* refactor(javascript): break the semantic analysis import cycle Split shared semantic facts out of the analysis hub so projectors of effects, evaluation, and identity no longer import one another in a cycle. * refactor(javascript): share the import specifier reader Read static imports in one module used by the boundary checker and both cycle guards, so those rules cannot drift apart. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
940d71a45a
commit
249b90c5e3
33 files changed
+1043
-772
No files matched your search
@@ -11,11 +11,13 @@ import {
|
||||
semanticStaticPropertyKey,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import { semanticScopeId } from "./javascriptSemanticState.js";
|
||||
import type {
|
||||
JavaScriptSemanticBindingState,
|
||||
JavaScriptSemanticAnalysisState,
|
||||
JavaScriptSemanticEffectTiming,
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticResolutionBlocked,
|
||||
semanticScopeId,
|
||||
type JavaScriptSemanticBindingState,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticEffectTiming,
|
||||
} from "./javascriptSemanticState.js";
|
||||
|
||||
interface StatementPosition {
|
||||
@@ -1207,3 +1209,100 @@ export const semanticEscapeFollowsCapture = (
|
||||
positionPrecedes(capturePosition, escapePosition)
|
||||
);
|
||||
};
|
||||
|
||||
/** Explicit wrapper facts and program `var` redeclarations retain their lexical identity. */
|
||||
export const commonJsWrapperBindingDeclaration = (
|
||||
node: t.Node,
|
||||
name: string,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): JavaScriptSemanticBindingState | undefined => {
|
||||
if (
|
||||
!["module", "exports", "require", "__dirname", "__filename"].includes(
|
||||
name,
|
||||
) ||
|
||||
!t.isIdentifier(node, { name })
|
||||
)
|
||||
return undefined;
|
||||
const binding = resolveSemanticBindingState(state, node, name);
|
||||
if (binding === undefined) return undefined;
|
||||
const scope = state.scopesById.get(binding.scopeId);
|
||||
if (
|
||||
scope?.kind !== "program" ||
|
||||
![...binding.declarationKinds].every((kind) => kind === "var")
|
||||
)
|
||||
return undefined;
|
||||
// The index owns explicit wrapper parameters, including ones with no source
|
||||
// redeclaration. Babel's module classification does not invalidate that fact.
|
||||
if (binding.hostOrigin === "commonjs-wrapper") return binding;
|
||||
// Without an explicit host fact, preserve the legacy script-var inference.
|
||||
return state.program.sourceType === "script" &&
|
||||
binding.declarationKinds.size > 0
|
||||
? binding
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/** Retained value and pattern stores share one source-ordered write boundary. */
|
||||
export const semanticBindingHasPriorValueWrite = (
|
||||
site: t.Node,
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
if (binding.definitions.some(({ kind }) => kind === "function")) return true;
|
||||
const writes = state.bindingWrites.get(binding.bindingId) ?? [];
|
||||
const read = semanticBindingAtRead(binding, site, state.parentsByNode);
|
||||
const retained =
|
||||
read.status === "ordered"
|
||||
? new Set([
|
||||
...read.binding.initializers,
|
||||
...read.binding.referenceInitializers,
|
||||
])
|
||||
: undefined;
|
||||
const representedWrites = new Set<t.Node>();
|
||||
const retainedWrites = new Set<t.Node>();
|
||||
for (const source of [
|
||||
...binding.initializers,
|
||||
...binding.referenceInitializers,
|
||||
]) {
|
||||
let owner: t.Node | undefined = source.node;
|
||||
while (owner !== undefined && !writes.includes(owner))
|
||||
owner = state.parentsByNode.get(owner);
|
||||
if (owner !== undefined) {
|
||||
representedWrites.add(owner);
|
||||
if (retained === undefined || retained.has(source))
|
||||
retainedWrites.add(owner);
|
||||
} else if (
|
||||
(retained === undefined || retained.has(source)) &&
|
||||
semanticEffectTiming(site, source.node, state) !== "after"
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return writes.some(
|
||||
(write) =>
|
||||
(!representedWrites.has(write) || retainedWrites.has(write)) &&
|
||||
semanticEffectTiming(site, write, state) !== "after",
|
||||
);
|
||||
};
|
||||
|
||||
/** A program-level `var` redeclaration still denotes its untouched CJS wrapper parameter. */
|
||||
export const isUnmodifiedCommonJsWrapperBinding = (
|
||||
node: t.Node,
|
||||
name: string,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
const binding = commonJsWrapperBindingDeclaration(node, name, state);
|
||||
return (
|
||||
binding !== undefined &&
|
||||
!semanticBindingHasPriorValueWrite(node, binding, state)
|
||||
);
|
||||
};
|
||||
|
||||
/** Identify a global name only when no lexical or dynamic environment shadows it. */
|
||||
export const isUnshadowedGlobal = (
|
||||
node: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
name: string,
|
||||
): boolean =>
|
||||
t.isIdentifier(node, { name }) &&
|
||||
(resolveSemanticBindingState(state, node, name) === undefined
|
||||
? !semanticResolutionBlocked(state, node, name)
|
||||
: isUnmodifiedCommonJsWrapperBinding(node, name, state));
|
||||
Reference in new issue
Block a user