Files
rea/src/domain/javascript/javascriptSemanticDataEffectHelpers.ts
T
morluto 04672f2b91 fix(javascript): preserve builtin identity and share alias traversal
Track module writes and escapes at their owner, preserve capture and reassignment ordering, and share CommonJS/default namespace identity without erasing parent-slot changes. Carry write sites into ambient alias resolution and use parsed write targets across consumers. Deduplicate returned-reference work with cycle context; retain unchanged temporal selection identity so provenance memoization stays effective.

Reviewed fixes include #2041, #2465, #2830, #2881, and #2948. Remove superseded mutation representations and the ineffective per-markValue dedup layer.

Validation: 380 focused JavaScript tests passed under a 384 MiB heap cap, including the previously aborting 30-level/128-call alias regression. Six public CLI/MCP builtin tests passed. Exact pdf.js completed-workflow performance remains unmeasured.
2026-10-14 08:44:52 +08:00

951 lines
30 KiB
TypeScript

import * as t from "@babel/types";
import { BUILTIN_RESOURCE_METHODS } from "./javascriptBuiltinResourceMethods.js";
import { semanticBuiltinNamespacePath } from "./javascriptSemanticProvenance.js";
import {
semanticRequireOrigin,
semanticRequireExportsIdentity,
semanticGlobalProcessOrigin,
semanticMutableBuiltinOrigin,
} from "./javascriptSemanticRequireOrigin.js";
import type { JavaScriptModuleOrigin } from "./javascriptSemanticIr.js";
import {
evaluateSemanticProvenance,
evaluateSemanticProvenanceAtRead,
} from "./javascriptSemanticValues.js";
import {
semanticPropertyPathKeyMatches,
type JavaScriptSemanticPropertyPath,
} from "./javascriptSemanticPropertyPaths.js";
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
import { semanticTopLevelEffectFollowsCapture } from "./javascriptSemanticMutationInitializers.js";
import {
resolveSemanticBindingState,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
type JavaScriptSemanticMemberEffect,
type JavaScriptSemanticRequiredModuleEffect,
} from "./javascriptSemanticState.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import {
readExactJavaScriptLiteral,
semanticStaticPropertyKey,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
export interface SemanticProjectedBinding {
readonly bindingId: string;
readonly identifier: t.Identifier;
readonly projectionPath: readonly (string | number | null)[];
readonly resolution: "complete" | "partial";
}
/** Resolve bindings written by the nearest variable or assignment owner. */
export const assignedSemanticResultBindings = (
node: t.Node,
ancestors: readonly t.Node[],
state: JavaScriptSemanticAnalysisState,
): SemanticProjectedBinding[] => {
for (let index = ancestors.length - 1; index >= 0; index -= 1) {
const owner = ancestors[index];
if (owner === undefined) continue;
let pattern: t.Node | undefined;
let source: t.Node | null = null;
if (t.isVariableDeclarator(owner)) {
pattern = owner.id;
source = owner.init ?? null;
} else if (t.isAssignmentExpression(owner)) {
pattern = owner.left;
source = owner.right;
}
if (
pattern !== undefined &&
source !== null &&
assignedSourceIs(node, source)
)
return projectedPatternBindings(pattern, state);
if (semanticCallableIdForNode(owner) !== null) return [];
}
return [];
};
const assignedSourceIs = (node: t.Node, source: t.Node): boolean => {
let value = source;
while (true) {
if (t.isAwaitExpression(value)) value = value.argument;
else {
const unwrapped = unwrapJavaScriptExpression(value).node;
if (unwrapped === value) break;
value = unwrapped;
}
}
return value === node;
};
const projectedPatternBindings = (
pattern: t.Node,
state: JavaScriptSemanticAnalysisState,
): SemanticProjectedBinding[] => {
const output: SemanticProjectedBinding[] = [];
const visit = (
current: t.Node,
path: readonly (string | number | null)[],
partial: boolean,
): void => {
if (t.isIdentifier(current)) {
const binding = resolveSemanticBindingState(state, current, current.name);
if (binding !== undefined)
output.push({
bindingId: binding.bindingId,
identifier: current,
projectionPath: path,
resolution: partial ? "partial" : "complete",
});
return;
}
if (t.isAssignmentPattern(current)) return visit(current.left, path, true);
if (t.isRestElement(current))
return visit(current.argument, [...path, null], true);
if (t.isObjectPattern(current)) {
for (const property of current.properties) {
if (t.isRestElement(property))
visit(property.argument, [...path, null], true);
else
visit(
property.value,
[
...path,
semanticStaticPropertyKey(property.key, property.computed),
],
partial ||
semanticStaticPropertyKey(property.key, property.computed) ===
null,
);
}
} else if (t.isArrayPattern(current)) {
current.elements.forEach((element, index) => {
if (element !== null) visit(element, [...path, index], partial);
});
}
};
visit(pattern, [], false);
return output;
};
export const semanticProjectedBindingFacts = (
bindings: readonly SemanticProjectedBinding[],
): Omit<SemanticProjectedBinding, "identifier">[] =>
bindings.map(({ identifier: _identifier, ...fact }) => fact);
/** Return a scalar owner only for an unprojected identifier result. */
export const scalarAssignedResultBinding = (
bindings: readonly SemanticProjectedBinding[],
): string | null =>
bindings.length === 1 && bindings[0]?.projectionPath.length === 0
? (bindings[0]?.bindingId ?? null)
: null;
/** Shared mutable stacks reset before each deterministic data-effect pass. */
export interface DataEffectTraversalContext {
readonly state: JavaScriptSemanticAnalysisState;
readonly callableIds: ReadonlySet<string>;
readonly callableStack: string[];
readonly ancestors: t.Node[];
}
/** Resolve built-in methods from the shared, unambiguous binding provenance. */
export const builtinDataEffectMethod = (
callee: t.Node,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
): string | null => {
const expression = unwrapJavaScriptExpression(callee).node;
if (expression !== callee)
return builtinDataEffectMethod(expression, state, specifiers);
if (t.isSequenceExpression(expression)) {
const final = expression.expressions.at(-1);
return final === undefined
? null
: builtinDataEffectMethod(final, state, specifiers);
}
const required = semanticRequireOrigin(callee, state);
if (required !== undefined) {
const origin = builtinNamespaceOrigin(required.origin);
return origin.importedPath.length === 1 &&
specifiers.some(
(specifier) => specifier.replace(/^node:/u, "") === origin.specifier,
) &&
!requiredModuleChanged(
required.origin.specifier,
required.origin.importedPath,
callee,
state,
true,
)
? (origin.importedPath[0] ?? null)
: null;
}
if (t.isIdentifier(callee)) {
const binding = resolveSemanticBindingState(state, callee, callee.name);
const path = builtinBindingPath(
binding,
state,
specifiers,
callee,
[],
true,
);
return path?.length === 1 ? (path[0] ?? null) : null;
}
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
return null;
if (!t.isIdentifier(callee.object)) {
const method = semanticStaticPropertyKey(callee.property, callee.computed);
const receiver = semanticBoundMemberPath(callee.object, state);
if (method !== null && receiver !== undefined) {
const provenance = evaluateSemanticProvenance(receiver.binding, state);
if (provenance.status === "module") {
const origin = builtinNamespaceOrigin({
...provenance.origins[0],
importedPath: [
...provenance.origins[0].importedPath,
...receiver.path,
],
});
if (
specifiers.some(
(specifier) =>
specifier.replace(/^node:/u, "") === origin.specifier,
)
)
return builtinBindingPath(
receiver.binding,
state,
specifiers,
callee,
[...receiver.path, method],
true,
)?.length === 0
? method
: null;
}
}
const source = semanticExpressionModuleOrigin(callee.object, state);
if (method === null || source === undefined) return null;
const origin = builtinNamespaceOrigin(source);
return origin.importedPath.length === 0 &&
specifiers.some(
(specifier) => specifier.replace(/^node:/u, "") === origin.specifier,
) &&
!requiredModuleChanged(
origin.specifier,
[...origin.importedPath, method],
callee,
state,
true,
callee.object,
)
? method
: null;
}
const binding = resolveSemanticBindingState(
state,
callee.object,
callee.object.name,
);
const method = semanticStaticPropertyKey(callee.property, callee.computed);
if (method === null) return null;
const resolvedPath = builtinBindingPath(
binding,
state,
specifiers,
callee,
[method],
true,
);
if (resolvedPath?.length === 0) return method;
const initializer = binding?.initializers[0];
if (
initializer === undefined ||
binding?.initializers.length !== 1 ||
(!t.isMemberExpression(initializer.node) &&
!t.isOptionalMemberExpression(initializer.node))
) {
return null;
}
const provenance = evaluateSemanticProvenance(binding, state);
if (provenance.status !== "module") return null;
const origin = builtinNamespaceOrigin(provenance.origins[0]);
const selected = [...origin.importedPath, method];
return specifiers.some(
(specifier) => specifier.replace(/^node:/u, "") === origin.specifier,
) &&
builtinBindingPath(
binding,
state,
specifiers,
initializer.node,
[method],
true,
)?.length === 0 &&
!requiredModuleChanged(
provenance.origins[0].specifier,
selected,
callee,
state,
true,
initializer.node,
)
? method
: null;
};
const semanticBoundMemberPath = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
):
| {
readonly binding: JavaScriptSemanticBindingState;
readonly path: string[];
}
| undefined => {
const path: string[] = [];
let current = unwrapJavaScriptExpression(node).node;
while (
t.isMemberExpression(current) ||
t.isOptionalMemberExpression(current)
) {
const key = semanticStaticPropertyKey(current.property, current.computed);
if (key === null || !t.isNode(current.object)) return undefined;
path.unshift(key);
current = unwrapJavaScriptExpression(current.object).node;
}
if (!t.isIdentifier(current)) return undefined;
const binding = resolveSemanticBindingState(state, current, current.name);
return binding === undefined ? undefined : { binding, path };
};
type BuiltinBindingMemo = Map<t.Node, Map<string, readonly string[] | null>>;
const builtinBindingPath = (
binding: JavaScriptSemanticBindingState | undefined,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
capture: t.Node,
selectedPath: readonly string[] = [],
callableValue = false,
visited: ReadonlySet<string> = new Set(),
memo: BuiltinBindingMemo = new Map(),
provenanceCapture?: t.Identifier,
savedObjectCapture?: t.Node,
): readonly string[] | null => {
if (binding === undefined) return null;
const key = JSON.stringify([
binding.bindingId,
selectedPath,
callableValue,
provenanceCapture?.start,
provenanceCapture?.end,
savedObjectCapture?.start,
savedObjectCapture?.end,
]);
let captured = memo.get(capture);
if (captured?.has(key)) return captured.get(key) ?? null;
if (visited.has(binding.bindingId)) return null;
if (captured === undefined) {
captured = new Map();
memo.set(capture, captured);
}
const result = computeBuiltinBindingPath(
binding,
state,
specifiers,
capture,
selectedPath,
callableValue,
visited,
memo,
provenanceCapture,
savedObjectCapture,
);
captured.set(key, result);
return result;
};
const computeBuiltinBindingPath = (
binding: JavaScriptSemanticBindingState | undefined,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
capture: t.Node,
selectedPath: readonly string[] = [],
callableValue = false,
visited: ReadonlySet<string>,
memo: BuiltinBindingMemo,
provenanceCapture?: t.Identifier,
savedObjectCapture?: t.Node,
): readonly string[] | null => {
if (binding === undefined || visited.has(binding.bindingId)) return null;
// Provenance identifies the module, independently of whether a selected slot
// still refers to its original built-in after member mutation or escape.
if (
binding.memberWrites.some(
({ path, node }) =>
affectsBuiltinPath(path, selectedPath) &&
!(
semanticTopLevelEffectFollowsCapture(
path.length === 0 && provenanceCapture !== undefined
? provenanceCapture
: capture,
node,
state.parentsByNode,
) ||
(savedObjectCapture !== undefined &&
path.length < selectedPath.length &&
semanticTopLevelEffectFollowsCapture(
savedObjectCapture,
node,
state.parentsByNode,
))
),
) ||
builtinEscapes(binding, state).some(
({ path, node }) =>
affectsBuiltinPath(path, selectedPath) &&
// A function-value escape cannot replace its native [[Call]]. An
// escaped containing object can still replace the selected method.
(!callableValue || path.length < selectedPath.length) &&
!semanticTopLevelEffectFollowsCapture(
capture,
node,
state.parentsByNode,
),
)
)
return null;
const provenance =
provenanceCapture === undefined
? evaluateSemanticProvenance(binding, state)
: evaluateSemanticProvenanceAtRead(binding, provenanceCapture, state);
if (provenance.status !== "module") return null;
const { importedPath } = provenance.origins[0];
const namespace = builtinNamespaceOrigin(provenance.origins[0]);
if (
!specifiers.some((item) => {
const name = item.replace(/^node:/u, "");
return (
name === namespace.specifier ||
(namespace.specifier === "fs" &&
selectedPath[0] === "promises" &&
name === "fs/promises")
);
})
)
return null;
let resolvedPath: readonly string[] = importedPath;
for (const origin of binding.directOrigins) {
const shared = semanticMutableBuiltinOrigin(origin, selectedPath);
if (
shared !== undefined &&
requiredModuleChanged(
shared.specifier,
shared.path,
capture,
state,
callableValue,
)
)
return null;
}
const ancestors = new Set([...visited, binding.bindingId]);
for (const initializer of binding.initializers) {
const pending = [
{
node: initializer.node,
path: [...initializer.projection.map(String), ...selectedPath],
savedObjectCapture:
selectedPath.length > 0 &&
(t.isMemberExpression(initializer.node) ||
t.isOptionalMemberExpression(initializer.node))
? initializer.node
: savedObjectCapture,
capture:
callableValue &&
selectedPath.length === 0 &&
initializer.projection.length > 0
? initializer.node
: capture,
},
];
while (pending.length > 0) {
const current = pending.pop()!;
const node = unwrapJavaScriptExpression(current.node).node;
if (t.isIdentifier(node)) {
const source = resolveSemanticBindingState(state, node, node.name);
const sourcePath = builtinBindingPath(
source,
state,
specifiers,
current.capture,
current.path,
callableValue,
ancestors,
memo,
node,
current.savedObjectCapture,
);
if (sourcePath === null) return null;
// Normalize ESM default namespaces at their owner before projecting an alias.
resolvedPath = [...sourcePath, ...current.path].slice(
0,
sourcePath.length + current.path.length - selectedPath.length,
);
} else if (
t.isMemberExpression(node) ||
t.isOptionalMemberExpression(node)
) {
const key = semanticStaticPropertyKey(node.property, node.computed);
if (key === null) return null;
pending.push({
node: node.object,
path: [key, ...current.path],
savedObjectCapture: current.savedObjectCapture,
// Freeze a selected function, not later member reads through a saved object.
capture:
callableValue && selectedPath.length === 0 ? node : current.capture,
});
} else if (t.isSequenceExpression(node)) {
const final = node.expressions.at(-1);
if (final === undefined) return null;
pending.push({ ...current, node: final });
} else if (t.isAssignmentExpression(node) && node.operator === "=") {
pending.push({ ...current, node: node.right });
} else if (t.isConditionalExpression(node)) {
pending.push(
{ ...current, node: node.consequent },
{ ...current, node: node.alternate },
);
} else if (t.isLogicalExpression(node)) {
pending.push(
{ ...current, node: node.left },
{ ...current, node: node.right },
);
} else {
const required = semanticRequireOrigin(node, state);
if (
required !== undefined &&
requiredModuleChanged(
required.origin.specifier,
[...required.origin.importedPath, ...current.path],
current.capture,
state,
callableValue,
current.savedObjectCapture,
)
)
return null;
}
}
}
// Only a direct ESM default import denotes the built-in namespace;
// require("node:fs").default is a literal property, not interop syntax.
return binding.directOrigins.length > 0 &&
semanticBuiltinNamespacePath(importedPath)
? []
: builtinNamespaceOrigin({
...provenance.origins[0],
importedPath: resolvedPath,
}).importedPath;
};
/** Interpret known Node namespace aliases without changing source provenance. */
const builtinNamespaceOrigin = (
origin: JavaScriptModuleOrigin,
): JavaScriptModuleOrigin => {
const specifier = origin.specifier.replace(/^node:/u, "");
const importedPath =
origin.resolutionMode !== "require" && origin.importedPath[0] === "default"
? origin.importedPath.slice(1)
: origin.importedPath;
return specifier === "fs" && importedPath[0] === "promises"
? {
...origin,
specifier: "fs/promises",
importedPath: importedPath.slice(1),
}
: { ...origin, specifier, importedPath };
};
const affectsBuiltinPath = (
path: JavaScriptSemanticPropertyPath,
selected: readonly string[],
): boolean =>
path.length <= selected.length &&
path.every((key, index) =>
semanticPropertyPathKeyMatches(key, selected[index]!),
);
const BUILTIN_RECEIVER_METHODS: Readonly<Record<string, ReadonlySet<string>>> =
{
fs: new Set([
"readFile",
"readFileSync",
"writeFile",
"writeFileSync",
"close",
"closeSync",
"existsSync",
...BUILTIN_RESOURCE_METHODS.fs,
]),
"fs/promises": new Set([
"readFile",
"writeFile",
...BUILTIN_RESOURCE_METHODS["fs/promises"],
]),
net: new Set(BUILTIN_RESOURCE_METHODS.net),
tls: new Set(BUILTIN_RESOURCE_METHODS.tls),
http: new Set(["request", "get"]),
https: new Set(["request", "get"]),
child_process: new Set(["spawn", "exec", "execFile", "fork"]),
process: new Set(["cwd", "uptime", "hrtime", "nextTick"]),
timers: new Set([
"setTimeout",
"setInterval",
"setImmediate",
"clearTimeout",
"clearInterval",
"clearImmediate",
]),
};
interface BuiltinEffectIndex {
readonly writesByModule: Map<
string,
readonly JavaScriptSemanticRequiredModuleEffect[]
>;
readonly moduleEffects: Map<
string,
readonly JavaScriptSemanticRequiredModuleEffect[]
>;
readonly bindingEscapes: Map<
string,
readonly JavaScriptSemanticMemberEffect[]
>;
readonly receivers: WeakMap<t.Node, boolean>;
}
// Derived passes run after mutation collection. These indexes share that state's
// lifetime and never cache facts while its mutation owner is still producing them.
const builtinEffectIndexes = new WeakMap<
JavaScriptSemanticAnalysisState,
BuiltinEffectIndex
>();
const builtinEffectIndex = (
state: JavaScriptSemanticAnalysisState,
): BuiltinEffectIndex => {
let index = builtinEffectIndexes.get(state);
if (index === undefined) {
index = {
writesByModule: new Map(
[...state.requiredModuleEffects].map(([name, effects]) => [
name,
effects.filter(({ kind }) => kind === "write"),
]),
),
moduleEffects: new Map(),
bindingEscapes: new Map(),
receivers: new WeakMap(),
};
builtinEffectIndexes.set(state, index);
}
return index;
};
const builtinEscapes = (
binding: JavaScriptSemanticBindingState,
state: JavaScriptSemanticAnalysisState,
): readonly JavaScriptSemanticMemberEffect[] => {
const cache = builtinEffectIndex(state).bindingEscapes;
let effects = cache.get(binding.bindingId);
if (effects === undefined) {
effects = binding.escapedPaths.filter(
({ node }) => !originalBuiltinReceiver(node, state),
);
cache.set(binding.bindingId, effects);
}
return effects;
};
const builtinModuleEffects = (
specifier: string,
state: JavaScriptSemanticAnalysisState,
): readonly JavaScriptSemanticRequiredModuleEffect[] => {
const cache = builtinEffectIndex(state).moduleEffects;
let effects = cache.get(specifier);
if (effects === undefined) {
effects = (state.requiredModuleEffects.get(specifier) ?? []).filter(
({ kind, node }) =>
kind === "write" || !originalBuiltinReceiver(node, state),
);
if (specifier === "fs/promises") {
const sharedFsEscapes = (state.requiredModuleEffects.get("fs") ?? [])
.filter(
({ kind, path, node }) =>
kind === "escape" &&
(path.length === 0 || path[0] === "promises") &&
!originalBuiltinReceiver(node, state),
)
.map((effect) => ({
...effect,
path:
effect.path[0] === "promises" ? effect.path.slice(1) : effect.path,
}));
effects = [...effects, ...sharedFsEscapes];
}
cache.set(specifier, effects);
}
return effects;
};
/** Check shared exports at the use or method-capture point, not at every later call. */
const requiredModuleChanged = (
specifier: string,
selected: readonly string[],
capture: t.Node,
state: JavaScriptSemanticAnalysisState,
callableValue = false,
savedObjectCapture?: t.Node,
): boolean => {
const identity = semanticRequireExportsIdentity(specifier, selected);
return builtinModuleEffects(identity.specifier, state).some(
({ path, node, kind }) =>
!(
savedObjectCapture !== undefined &&
path.length < identity.path.length &&
semanticTopLevelEffectFollowsCapture(
savedObjectCapture,
node,
state.parentsByNode,
)
) &&
affectsBuiltinPath(path, identity.path) &&
!semanticTopLevelEffectFollowsCapture(
capture,
node,
state.parentsByNode,
) &&
(kind === "write" ||
!callableValue ||
path.length < identity.path.length),
);
};
/** Calling an unchanged built-in as a member is not an escape to unknown code. */
const originalBuiltinReceiver = (
node: t.Node | undefined,
state: JavaScriptSemanticAnalysisState,
): boolean => {
if (node === undefined) return false;
const receivers = builtinEffectIndex(state).receivers;
const previous = receivers.get(node);
if (previous !== undefined) return previous;
const result = computeOriginalBuiltinReceiver(node, state);
receivers.set(node, result);
return result;
};
const computeOriginalBuiltinReceiver = (
node: t.Node | undefined,
state: JavaScriptSemanticAnalysisState,
): boolean => {
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
return false;
const callee = node.callee;
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
return false;
const receiver = t.isIdentifier(callee.object)
? resolveSemanticBindingState(state, callee.object, callee.object.name)
: undefined;
const provenance =
receiver === undefined
? undefined
: evaluateSemanticProvenance(receiver, state);
const sourceOrigin =
provenance?.status === "module"
? provenance.origins[0]
: (semanticExpressionModuleOrigin(callee.object, state) ??
semanticRequireOrigin(callee.object, state)?.origin ??
semanticGlobalProcessOrigin(callee.object, state));
if (sourceOrigin === undefined) return false;
const origin = builtinNamespaceOrigin(sourceOrigin);
if (origin.importedPath.length !== 0) return false;
const method = semanticStaticPropertyKey(callee.property, callee.computed);
const identity = semanticRequireExportsIdentity(origin.specifier, [
method ?? "",
]);
if (
method === null ||
!BUILTIN_RECEIVER_METHODS[origin.specifier]?.has(method) ||
receiver?.memberWrites.some(
({ path, node: write }) =>
affectsBuiltinPath(path, [method]) &&
!semanticTopLevelEffectFollowsCapture(node, write, state.parentsByNode),
) ||
(
builtinEffectIndex(state).writesByModule.get(identity.specifier) ?? []
).some(
({ path, node: write }) =>
affectsBuiltinPath(path, identity.path) &&
!semanticTopLevelEffectFollowsCapture(node, write, state.parentsByNode),
)
)
return false;
return !node.arguments.some((argument) => {
if (!t.isIdentifier(argument)) return false;
const passed = resolveSemanticBindingState(state, argument, argument.name);
if (passed === undefined) return false;
const passedOrigin = evaluateSemanticProvenance(passed, state);
return (
passedOrigin.status === "module" &&
builtinNamespaceOrigin(passedOrigin.origins[0]).specifier ===
origin.specifier &&
builtinNamespaceOrigin(passedOrigin.origins[0]).importedPath.length === 0
);
});
};
const semanticExpressionModuleOrigin = (
expression: t.Node,
state: JavaScriptSemanticAnalysisState,
visited = new Set<string>(),
): JavaScriptModuleOrigin | undefined => {
const node = unwrapJavaScriptExpression(expression).node;
const required = semanticRequireOrigin(node, state);
if (required !== undefined) return required.origin;
if (t.isIdentifier(node)) {
const binding = resolveSemanticBindingState(state, node, node.name);
if (binding === undefined || visited.has(binding.bindingId))
return undefined;
const provenance = evaluateSemanticProvenance(binding, state);
return provenance.status === "module" ? provenance.origins[0] : undefined;
}
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
const base = semanticExpressionModuleOrigin(node.object, state, visited);
const key = semanticStaticPropertyKey(node.property, node.computed);
return base === undefined || key === null
? undefined
: { ...base, importedPath: [...base.importedPath, key] };
}
return undefined;
};
/** Recognize global or node:process aliases without accepting shadowing. */
export const isSemanticProcessObject = (
node: t.Expression | t.Super,
state: JavaScriptSemanticAnalysisState,
family: "env" | "argv",
key?: string | null,
): boolean => {
if (!t.isIdentifier(node)) return false;
const binding = resolveSemanticBindingState(state, node, node.name);
const selected = key === undefined || key === null ? [family] : [family, key];
if (binding === undefined)
return (
semanticGlobalProcessOrigin(node, state) !== undefined &&
!requiredModuleChanged("process", selected, node, state)
);
return (
builtinBindingPath(
binding,
state,
["process", "node:process"],
node,
selected,
)?.length === 0
);
};
/** Resolve the nearest direct variable/assignment owner before a callable. */
export const outerDataEffectBinding = (
node: t.Node,
context: DataEffectTraversalContext,
): string | null => {
return scalarAssignedResultBinding(
assignedSemanticResultBindings(node, context.ancestors, context.state),
);
};
/** Traverse with exact callable and ancestor stacks. */
export const traverseDataEffects = (
program: t.Program,
context: DataEffectTraversalContext,
visit: (node: t.Node, parent: t.Node | null) => void,
): void => {
context.callableStack.length = 0;
context.ancestors.length = 0;
traverseJavaScriptAst(program, {
enter: (node, parent) => {
const callableId = semanticCallableIdForNode(node);
if (callableId !== null && context.callableIds.has(callableId))
context.callableStack.push(callableId);
visit(node, parent);
context.ancestors.push(node);
},
exit: (node) => {
context.ancestors.pop();
const callableId = semanticCallableIdForNode(node);
if (callableId !== null && context.callableStack.at(-1) === callableId)
context.callableStack.pop();
},
});
};
/** Resolve one direct identifier argument to a lexical binding. */
export const dataEffectArgumentBindingId = (
node:
| t.Expression
| t.SpreadElement
| t.JSXNamespacedName
| t.ArgumentPlaceholder
| undefined,
state: JavaScriptSemanticAnalysisState,
): string | null =>
t.isIdentifier(node)
? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
: null;
/** Read one JSON primitive default without evaluating code. */
export const dataEffectPrimitive = (
node: t.Node,
): string | number | boolean | null => {
if (t.isStringLiteral(node) || t.isNumericLiteral(node)) return node.value;
if (t.isBooleanLiteral(node)) return node.value;
return null;
};
/** Read one static string or expression-free template. */
export const dataEffectLiteralString = (
node:
| t.Expression
| t.SpreadElement
| t.JSXNamespacedName
| t.ArgumentPlaceholder
| undefined,
): string | null => {
if (node === undefined) return null;
const literal = readExactJavaScriptLiteral(node);
if (literal.found && typeof literal.value === "string") return literal.value;
return t.isTemplateLiteral(node) && node.expressions.length === 0
? (node.quasis[0]?.value.raw ?? null)
: null;
};
/** Read a member callee from call or construction syntax. */
export const dataEffectMemberCallee = (
node: t.CallExpression | t.OptionalCallExpression | t.NewExpression,
): t.MemberExpression | t.OptionalMemberExpression | null =>
t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
? node.callee
: null;