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:
morlutoandCursor Agent authored and GitHub committed 2026-10-30 06:07:49 +08:00
1 parent 940d71a45a
commit 249b90c5e3
33 files changed
+1043 -772

No files matched your search

@@ -1,31 +1,20 @@
import * as t from "@babel/types";
import { javaScriptProgramStrictness } from "./javascriptSourceParser.js";
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
import { createAmbientPathResolver } from "./javascriptSemanticAmbientWrites.js";
import { createSemanticModuleInterop } from "./javascriptModuleInterop.js";
import {
isUnshadowedGlobal,
semanticEffectTiming,
semanticBindingEffectFollowsCapture,
semanticBindingEffectFollowsRead,
semanticReadPrecedesEffectWithinActivation,
} from "./javascriptSemanticMutationInitializers.js";
import {
isUnshadowedGlobal,
resolveSemanticBindingState,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
/** One source owns sites and the minimal scope tree reaching them; O(AST) storage. */
export interface JavaScriptOpaqueScopeEffects {
/** Cheap source evidence collected with declarations, before alias queries. */
mayReferenceGlobalEval: boolean;
readonly sites: WeakSet<t.Node>;
readonly sloppySites: WeakSet<t.Node>;
readonly byScope: Map<string, t.CallExpression[]>;
readonly children: Map<string, Set<string>>;
readonly globalSites: (t.CallExpression | t.OptionalCallExpression)[];
}
export type { JavaScriptOpaqueScopeEffects } from "./javascriptSemanticState.js";
export const indexSemanticOpaqueEffects = (
candidates: readonly {
@@ -59,11 +48,30 @@ export const indexSemanticOpaqueEffects = (
* neither retain a second call list nor create another alias resolver. */
export const createSemanticOpaqueGlobalObserver = (
state: JavaScriptSemanticAnalysisState,
dependencies: {
createAmbientPathResolver: (
state: JavaScriptSemanticAnalysisState,
observeGlobals: boolean,
) => (
node: t.Node,
site: t.Node | undefined,
) => { paths: readonly (readonly (string | null)[])[] };
createSemanticModuleInterop: (
state: JavaScriptSemanticAnalysisState,
options?: {
intrinsicPath: (node: t.Node) => readonly string[] | undefined;
},
) => {
intrinsicInvocation: (
node: t.CallExpression | t.OptionalCallExpression,
) => { readonly path: readonly string[] } | undefined;
};
},
): ((node: t.Node) => void) | undefined => {
if (!state.opaqueEffects.mayReferenceGlobalEval) return undefined;
const ambientPaths = createAmbientPathResolver(state, true);
const ambientPaths = dependencies.createAmbientPathResolver(state, true);
let invocationSite: t.Node | undefined;
const interop = createSemanticModuleInterop(state, {
const interop = dependencies.createSemanticModuleInterop(state, {
intrinsicPath: (node) => {
const paths = ambientPaths(node, invocationSite).paths;
const path = paths.find((path) => path[1] === "eval") ?? paths[0];