fix(javascript): preserve loader authority for exact suffixes (#9477)

* fix(javascript): require exact loader suffix case

* fix(javascript): share exact loader mode across import resolution

* test(process): align symlink fixture with filesystem checkpoints

* test(javascript): normalize the module resolution oracle path separators

* test(frida): assert the CLI spawn Evidence contract

* test(mcp): use an unsupported analysis-view parent

* test(browser): separate source-location line bounds from parity

* fix(javascript): preserve uncertain loader provenance

* test(javascript): cover uncertain require resolution

* fix(javascript): retain conditional Electron evidence

* fix(javascript): preserve timed semantic provenance

* fix(javascript): retain conditional builtin effects

* test(javascript): distinguish bundled host authority

* fix(javascript): preserve nested loader invocation identity

* test(javascript): qualify Electron fixture authority

* fix(javascript): preserve loader invocation authority

* fix(javascript): respect source order for resolver escapes

* fix(javascript): check nested loader callable mutations

* fix(javascript): retain conditional constructor requests

* fix(reference): retain constructor loader relationships

* fix(reference): follow nested loader callable paths

* refactor(javascript): share callable mutation interpretation

* fix(javascript): require exact package loader suffixes

* refactor(javascript): unify conditional relation evidence

* refactor(javascript): unify conditional node evidence

* fix(javascript): retain unknown authority for mixed case sources

* fix(javascript): preserve loader uncertainty for dotfiles

* fix(reference): classify javascript suffix dotfiles

* test(reference): cover suffix dotfiles through cli

* fix(javascript): retain equivalent conditional built-in origins

* fix(javascript): preserve wrapped resolver operation

* fix(javascript): retain nested conditional built-in receivers

* test(javascript): declare CommonJS in native addon fixtures

* fix(javascript): retain wrapped resolver candidates

* style: format merged native command schema

* fix(javascript): preserve uncertain loader authority

* test(javascript): require candidate preload evidence

* test(javascript): cover reference loader member writes

---------

Co-authored-by: ShuaixinHuang <1836656514@qq.com>
This commit is contained in:
morlutoandShuaixinHuang authored and GitHub committed 2026-11-09 13:21:24 +08:00
1 parent 78a84d75c0
commit b46f4fcebd
112 files changed
+5156 -797

No files matched your search

@@ -4,6 +4,7 @@ import { BUILTIN_RESOURCE_METHODS } from "./javascriptBuiltinResourceMethods.js"
import { semanticBuiltinNamespacePath } from "./javascriptSemanticProvenance.js";
import {
semanticRequireOrigin,
semanticRequireOriginCandidate,
semanticRequireExportsIdentity,
semanticGlobalProcessOrigin,
semanticMutableBuiltinOrigin,
@@ -22,10 +23,14 @@ import {
} from "./javascriptSemanticPropertyPaths.js";
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
import { semanticTopLevelEffectFollowsCapture } from "./javascriptSemanticMutationInitializers.js";
import {
semanticBindingAtRead,
semanticTopLevelEffectFollowsCapture,
} from "./javascriptSemanticMutationInitializers.js";
import { createSemanticCallableIdStack } from "./javascriptSemanticCallableStack.js";
import {
resolveSemanticBindingState,
semanticStableInitializer,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
type JavaScriptSemanticMemberEffect,
@@ -171,17 +176,70 @@ export const builtinDataEffectCallablePath = (
callee: t.Node,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
): readonly string[] | null => {
return builtinDataEffectCallablePathWithAuthority(
callee,
state,
specifiers,
false,
);
};
/** Recover a proven built-in or a conditional built-in identity. */
export const builtinDataEffectIdentity = (
callee: t.Node,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
): {
readonly path: readonly string[];
readonly builtinAuthority?: "candidate";
} | null => {
const proven = builtinDataEffectCallablePathWithAuthority(
callee,
state,
specifiers,
false,
);
if (proven !== null) return { path: proven };
const candidate = builtinDataEffectCallablePathWithAuthority(
callee,
state,
specifiers,
true,
);
return candidate === null
? null
: { path: candidate, builtinAuthority: "candidate" };
};
const builtinDataEffectCallablePathWithAuthority = (
callee: t.Node,
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
allowCandidate: boolean,
): readonly string[] | null => {
const expression = unwrapJavaScriptExpression(callee).node;
if (expression !== callee)
return builtinDataEffectCallablePath(expression, state, specifiers);
return builtinDataEffectCallablePathWithAuthority(
expression,
state,
specifiers,
allowCandidate,
);
if (t.isSequenceExpression(expression)) {
const final = expression.expressions.at(-1);
return final === undefined
? null
: builtinDataEffectCallablePath(final, state, specifiers);
: builtinDataEffectCallablePathWithAuthority(
final,
state,
specifiers,
allowCandidate,
);
}
const required = semanticRequireOrigin(callee, state);
const required = (
allowCandidate ? semanticRequireOriginCandidate : semanticRequireOrigin
)(callee, state);
if (required !== undefined) {
const origin = builtinNamespaceOrigin(required.origin);
return specifiers.some(
@@ -193,6 +251,9 @@ export const builtinDataEffectCallablePath = (
callee,
state,
true,
undefined,
allowCandidate,
allowCandidate && required.origin.loaderAuthority !== "candidate",
)
? origin.importedPath
: null;
@@ -204,6 +265,7 @@ export const builtinDataEffectCallablePath = (
state,
specifiers,
callee,
allowCandidate,
[],
true,
);
@@ -216,13 +278,14 @@ export const builtinDataEffectCallablePath = (
const receiver = semanticBoundMemberPath(callee.object, state);
if (method !== null && receiver !== undefined) {
const provenance = evaluateSemanticProvenance(receiver.binding, state);
if (provenance.status === "module") {
const sourceOrigin = builtinProvenanceOrigin(provenance, allowCandidate, [
...receiver.path,
method,
]);
if (sourceOrigin !== undefined) {
const origin = builtinNamespaceOrigin({
...provenance.origins[0],
importedPath: [
...provenance.origins[0].importedPath,
...receiver.path,
],
...sourceOrigin,
importedPath: [...sourceOrigin.importedPath, ...receiver.path],
});
if (
specifiers.some(
@@ -235,6 +298,7 @@ export const builtinDataEffectCallablePath = (
state,
specifiers,
callee,
allowCandidate,
[...receiver.path, method],
true,
) !== null
@@ -242,7 +306,12 @@ export const builtinDataEffectCallablePath = (
: null;
}
}
const source = semanticExpressionModuleOrigin(callee.object, state);
const source = semanticExpressionModuleOrigin(
callee.object,
state,
new Set(),
allowCandidate,
);
if (method === null || source === undefined) return null;
const origin = builtinNamespaceOrigin(source);
return specifiers.some(
@@ -255,6 +324,8 @@ export const builtinDataEffectCallablePath = (
state,
true,
callee.object,
allowCandidate,
allowCandidate && source.loaderAuthority !== "candidate",
)
? [...origin.importedPath, method]
: null;
@@ -271,6 +342,7 @@ export const builtinDataEffectCallablePath = (
state,
specifiers,
callee,
allowCandidate,
[method],
true,
);
@@ -285,8 +357,11 @@ export const builtinDataEffectCallablePath = (
return null;
}
const provenance = evaluateSemanticProvenance(binding, state);
if (provenance.status !== "module") return null;
const origin = builtinNamespaceOrigin(provenance.origins[0]);
const sourceOrigin = builtinProvenanceOrigin(provenance, allowCandidate, [
method,
]);
if (sourceOrigin === undefined) return null;
const origin = builtinNamespaceOrigin(sourceOrigin);
const selected = [...origin.importedPath, method];
return specifiers.some(
(specifier) => specifier.replace(/^node:/u, "") === origin.specifier,
@@ -296,16 +371,19 @@ export const builtinDataEffectCallablePath = (
state,
specifiers,
initializer.node,
allowCandidate,
[method],
true,
)?.length === 0 &&
!requiredModuleChanged(
provenance.origins[0].specifier,
sourceOrigin.specifier,
selected,
callee,
state,
true,
initializer.node,
allowCandidate,
allowCandidate && sourceOrigin.loaderAuthority !== "candidate",
)
? [method]
: null;
@@ -337,6 +415,7 @@ const builtinBindingPath = (
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
capture: t.Node,
allowCandidate: boolean,
selectedPath: readonly string[] = [],
callableValue = false,
visited: ReadonlySet<string> = new Set(),
@@ -366,6 +445,7 @@ const builtinBindingPath = (
state,
specifiers,
capture,
allowCandidate,
selectedPath,
callableValue,
visited,
@@ -382,6 +462,7 @@ const computeBuiltinBindingPath = (
state: JavaScriptSemanticAnalysisState,
specifiers: readonly string[],
capture: t.Node,
allowCandidate: boolean,
selectedPath: readonly string[] = [],
callableValue = false,
visited: ReadonlySet<string>,
@@ -390,6 +471,19 @@ const computeBuiltinBindingPath = (
savedObjectCapture?: t.Node,
): readonly string[] | null => {
if (binding === undefined || visited.has(binding.bindingId)) return null;
// A source-owned initializer is not available before its ordered execution.
// Hoisted imports and unresolved activation order retain their existing facts.
if (
binding.directOrigins.length === 0 &&
semanticStableInitializer(binding, state) !== undefined
) {
const read = semanticBindingAtRead(
binding,
provenanceCapture ?? capture,
state,
);
if (read.status === "ordered" && !read.hasDefiniteInitializer) 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 (
@@ -413,7 +507,7 @@ const computeBuiltinBindingPath = (
))
),
) ||
builtinEscapes(binding, state).some(
builtinEscapes(binding, state, allowCandidate).some(
({ path, node }) =>
affectsBuiltinPath(path, selectedPath) &&
// A function-value escape cannot replace its native [[Call]]. An
@@ -431,9 +525,14 @@ const computeBuiltinBindingPath = (
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]);
const sourceOrigin = builtinProvenanceOrigin(
provenance,
allowCandidate,
selectedPath,
);
if (sourceOrigin === undefined) return null;
const { importedPath } = sourceOrigin;
const namespace = builtinNamespaceOrigin(sourceOrigin);
if (
!specifiers.some((item) => {
const name = item.replace(/^node:/u, "");
@@ -457,6 +556,10 @@ const computeBuiltinBindingPath = (
capture,
state,
callableValue,
undefined,
allowCandidate,
allowCandidate && origin.loaderAuthority !== "candidate",
processSnapshotObjectRoot(origin, selectedPath),
)
)
return null;
@@ -491,6 +594,7 @@ const computeBuiltinBindingPath = (
state,
specifiers,
current.capture,
allowCandidate,
current.path,
callableValue,
ancestors,
@@ -535,7 +639,11 @@ const computeBuiltinBindingPath = (
{ ...current, node: node.right },
);
} else {
const required = semanticRequireOrigin(node, state);
const required = (
allowCandidate
? semanticRequireOriginCandidate
: semanticRequireOrigin
)(node, state);
if (
required !== undefined &&
requiredModuleChanged(
@@ -545,6 +653,8 @@ const computeBuiltinBindingPath = (
state,
callableValue,
current.savedObjectCapture,
allowCandidate,
allowCandidate && required.origin.loaderAuthority !== "candidate",
)
)
return null;
@@ -557,7 +667,7 @@ const computeBuiltinBindingPath = (
semanticBuiltinNamespacePath(importedPath)
? []
: builtinNamespaceOrigin({
...provenance.origins[0],
...sourceOrigin,
importedPath: resolvedPath,
}).importedPath;
};
@@ -580,6 +690,37 @@ const builtinNamespaceOrigin = (
: { ...origin, specifier, importedPath };
};
const builtinProvenanceOrigin = (
provenance: ReturnType<typeof evaluateSemanticProvenance>,
allowCandidate: boolean,
selectedPath: readonly string[],
): JavaScriptModuleOrigin | undefined => {
if (provenance.status === "module") return provenance.origins[0];
if (
!allowCandidate ||
provenance.status !== "ambiguous" ||
!provenance.origins.some((origin) => origin.loaderAuthority === "candidate")
)
return undefined;
const first = provenance.origins[0];
if (first === undefined) return undefined;
if (provenance.origins.length === 1) return first;
if (selectedPath[0] === "default") return undefined;
const normalized = builtinNamespaceOrigin(first);
return provenance.origins.every((origin) => {
const other = builtinNamespaceOrigin(origin);
return (
other.specifier === normalized.specifier &&
other.importedPath.length === normalized.importedPath.length &&
other.importedPath.every(
(part, index) => part === normalized.importedPath[index],
)
);
})
? first
: undefined;
};
const affectsBuiltinPath = (
path: JavaScriptSemanticPropertyPath,
selected: readonly string[],
@@ -639,6 +780,7 @@ interface BuiltinEffectIndex {
readonly JavaScriptSemanticMemberEffect[]
>;
readonly receivers: WeakMap<t.Node, boolean>;
readonly candidateReceivers: WeakMap<t.Node, boolean>;
}
// Derived passes run after mutation collection. These indexes share that state's
@@ -663,6 +805,7 @@ const builtinEffectIndex = (
moduleEffects: new Map(),
bindingEscapes: new Map(),
receivers: new WeakMap(),
candidateReceivers: new WeakMap(),
};
builtinEffectIndexes.set(state, index);
}
@@ -672,14 +815,16 @@ const builtinEffectIndex = (
const builtinEscapes = (
binding: JavaScriptSemanticBindingState,
state: JavaScriptSemanticAnalysisState,
allowCandidate = false,
): readonly JavaScriptSemanticMemberEffect[] => {
const cache = builtinEffectIndex(state).bindingEscapes;
let effects = cache.get(binding.bindingId);
const key = `${binding.bindingId}:${allowCandidate ? "candidate" : "proven"}`;
let effects = cache.get(key);
if (effects === undefined) {
effects = binding.escapedPaths.filter(
({ node }) => !originalBuiltinReceiver(node, state),
({ node }) => !originalBuiltinReceiver(node, state, allowCandidate),
);
cache.set(binding.bindingId, effects);
cache.set(key, effects);
}
return effects;
};
@@ -687,13 +832,16 @@ const builtinEscapes = (
const builtinModuleEffects = (
specifier: string,
state: JavaScriptSemanticAnalysisState,
allowCandidate = false,
): readonly JavaScriptSemanticRequiredModuleEffect[] => {
const cache = builtinEffectIndex(state).moduleEffects;
let effects = cache.get(specifier);
const key = `${specifier}:${allowCandidate ? "candidate" : "proven"}`;
let effects = cache.get(key);
if (effects === undefined) {
effects = (state.requiredModuleEffects.get(specifier) ?? []).filter(
({ kind, node }) =>
kind === "write" || !originalBuiltinReceiver(node, state),
kind === "write" ||
!originalBuiltinReceiver(node, state, allowCandidate),
);
if (specifier === "fs/promises") {
const sharedFsEscapes = (state.requiredModuleEffects.get("fs") ?? [])
@@ -701,7 +849,7 @@ const builtinModuleEffects = (
({ kind, path, node }) =>
kind === "escape" &&
(path.length === 0 || path[0] === "promises") &&
!originalBuiltinReceiver(node, state),
!originalBuiltinReceiver(node, state, allowCandidate),
)
.map((effect) => ({
...effect,
@@ -710,11 +858,29 @@ const builtinModuleEffects = (
}));
effects = [...effects, ...sharedFsEscapes];
}
cache.set(specifier, effects);
cache.set(key, effects);
}
return effects;
};
/** A named ESM process env/argv value snapshots the object, not later slot replacement. */
const processSnapshotObjectRoot = (
origin: JavaScriptModuleOrigin,
selected: readonly string[],
): "env" | "argv" | undefined => {
if (
origin.resolutionMode === "require" ||
origin.specifier.replace(/^node:/u, "") !== "process"
)
return undefined;
const [root, ...rest] = [...origin.importedPath, ...selected];
if ((root !== "env" && root !== "argv") || rest.length === 0)
return undefined;
// The default export is the mutable process object, not a snapshot slot.
if (origin.importedPath[0] === "default") return undefined;
return root;
};
/** Check shared exports at the use or method-capture point, not at every later call. */
const requiredModuleChanged = (
specifier: string,
@@ -723,10 +889,20 @@ const requiredModuleChanged = (
state: JavaScriptSemanticAnalysisState,
callableValue = false,
savedObjectCapture?: t.Node,
allowCandidate = false,
ignoreCandidateEffects = false,
snapshotObjectRoot?: "env" | "argv",
): boolean => {
const identity = semanticRequireExportsIdentity(specifier, selected);
return builtinModuleEffects(identity.specifier, state).some(
({ path, node, kind }) =>
return builtinModuleEffects(identity.specifier, state, allowCandidate).some(
({ path, node, kind, loaderAuthority }) =>
!(ignoreCandidateEffects && loaderAuthority === "candidate") &&
!(
snapshotObjectRoot !== undefined &&
kind === "write" &&
path.length === 1 &&
path[0] === snapshotObjectRoot
) &&
!(
savedObjectCapture !== undefined &&
path.length < identity.path.length &&
@@ -752,12 +928,14 @@ const requiredModuleChanged = (
const originalBuiltinReceiver = (
node: t.Node | undefined,
state: JavaScriptSemanticAnalysisState,
allowCandidate = false,
): boolean => {
if (node === undefined) return false;
const receivers = builtinEffectIndex(state).receivers;
const index = builtinEffectIndex(state);
const receivers = allowCandidate ? index.candidateReceivers : index.receivers;
const previous = receivers.get(node);
if (previous !== undefined) return previous;
const result = computeOriginalBuiltinReceiver(node, state);
const result = computeOriginalBuiltinReceiver(node, state, allowCandidate);
receivers.set(node, result);
return result;
};
@@ -765,25 +943,49 @@ const originalBuiltinReceiver = (
const computeOriginalBuiltinReceiver = (
node: t.Node | undefined,
state: JavaScriptSemanticAnalysisState,
allowCandidate: boolean,
): boolean => {
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
return false;
const callee = unwrapJavaScriptExpression(node.callee).node;
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
return false;
const receiver = t.isIdentifier(callee.object)
? resolveSemanticBindingState(state, callee.object, callee.object.name)
: undefined;
const boundReceiver = semanticBoundMemberPath(callee.object, state);
const receiver = boundReceiver?.binding;
const receiverPath = boundReceiver?.path ?? [];
const provenance =
receiver === undefined
? undefined
: evaluateSemanticProvenance(receiver, state);
const receiverMethod = semanticStaticPropertyKey(
callee.property,
callee.computed,
);
const boundOrigin =
provenance === undefined || receiverMethod === null
? undefined
: builtinProvenanceOrigin(provenance, allowCandidate, [
...receiverPath,
receiverMethod,
]);
const sourceOrigin =
provenance?.status === "module"
? provenance.origins[0]
: (semanticExpressionModuleOrigin(callee.object, state) ??
semanticRequireOrigin(callee.object, state)?.origin ??
semanticGlobalProcessOrigin(callee.object, state));
(boundOrigin === undefined
? undefined
: {
...boundOrigin,
importedPath: [...boundOrigin.importedPath, ...receiverPath],
}) ??
semanticExpressionModuleOrigin(
callee.object,
state,
new Set(),
allowCandidate,
) ??
(allowCandidate
? semanticRequireOriginCandidate(callee.object, state)
: semanticRequireOrigin(callee.object, state)
)?.origin ??
semanticGlobalProcessOrigin(callee.object, state);
if (sourceOrigin === undefined) return false;
const origin = builtinNamespaceOrigin(sourceOrigin);
const posixReceiver =
@@ -792,10 +994,15 @@ const computeOriginalBuiltinReceiver = (
origin.importedPath[0] === "posix";
if (origin.importedPath.length !== 0 && !posixReceiver) return false;
const method = semanticStaticPropertyKey(callee.property, callee.computed);
const identity = semanticRequireExportsIdentity(origin.specifier, [
...origin.importedPath,
method ?? "",
]);
// ESM namespace/named exports are snapshots, whereas the default object and
// CommonJS exports share mutable slots with require().
const sources =
provenance === undefined || boundOrigin === undefined
? [sourceOrigin]
: provenance.origins.map((source) => ({
...source,
importedPath: [...source.importedPath, ...receiverPath],
}));
if (
method === null ||
!BUILTIN_RECEIVER_METHODS[
@@ -803,16 +1010,38 @@ const computeOriginalBuiltinReceiver = (
]?.has(method) ||
receiver?.memberWrites.some(
({ path, node: write }) =>
affectsBuiltinPath(path, [method]) &&
affectsBuiltinPath(path, [...receiverPath, 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),
)
sources.some((source) => {
const shared =
method === null
? undefined
: semanticMutableBuiltinOrigin(source, [method]);
const identity =
shared === undefined
? undefined
: semanticRequireExportsIdentity(shared.specifier, shared.path);
return (
identity !== undefined &&
(
builtinEffectIndex(state).writesByModule.get(identity.specifier) ?? []
).some(
({ path, node: write, loaderAuthority }) =>
!(
allowCandidate &&
source.loaderAuthority !== "candidate" &&
loaderAuthority === "candidate"
) &&
affectsBuiltinPath(path, identity.path) &&
!semanticTopLevelEffectFollowsCapture(
node,
write,
state.parentsByNode,
),
)
);
})
)
return false;
return !node.arguments.some((argument) => {
@@ -821,7 +1050,11 @@ const computeOriginalBuiltinReceiver = (
if (passed === undefined) return false;
const passedOrigin = evaluateSemanticProvenance(passed, state);
return (
passedOrigin.status === "module" &&
(passedOrigin.status === "module" ||
(allowCandidate &&
passedOrigin.status === "ambiguous" &&
passedOrigin.origins.length === 1 &&
passedOrigin.origins[0]?.loaderAuthority === "candidate")) &&
builtinNamespaceOrigin(passedOrigin.origins[0]).specifier ===
origin.specifier &&
builtinNamespaceOrigin(passedOrigin.origins[0]).importedPath.length === 0
@@ -833,19 +1066,33 @@ const semanticExpressionModuleOrigin = (
expression: t.Node,
state: JavaScriptSemanticAnalysisState,
visited = new Set<string>(),
allowCandidate = false,
): JavaScriptModuleOrigin | undefined => {
const node = unwrapJavaScriptExpression(expression).node;
const required = semanticRequireOrigin(node, state);
const required = (
allowCandidate ? semanticRequireOriginCandidate : 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;
return provenance.status === "module" ||
(allowCandidate &&
provenance.status === "ambiguous" &&
provenance.origins.length === 1 &&
provenance.origins[0]?.loaderAuthority === "candidate")
? provenance.origins[0]
: undefined;
}
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
const base = semanticExpressionModuleOrigin(node.object, state, visited);
const base = semanticExpressionModuleOrigin(
node.object,
state,
visited,
allowCandidate,
);
const key = semanticStaticPropertyKey(node.property, node.computed);
return base === undefined || key === null
? undefined
@@ -875,11 +1122,49 @@ export const isSemanticProcessObject = (
state,
["process", "node:process"],
node,
false,
selected,
)?.length === 0
);
};
/** Preserve conditional process identity across loader and mutation uncertainty. */
export const semanticProcessObjectIdentity = (
node: t.Expression | t.Super,
state: JavaScriptSemanticAnalysisState,
family: "env" | "argv",
key?: string | null,
): { readonly builtinAuthority?: "candidate" } | null => {
if (isSemanticProcessObject(node, state, family, key)) return {};
if (!t.isIdentifier(node)) return null;
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,
false,
undefined,
false,
true,
)
? { builtinAuthority: "candidate" }
: null;
return builtinBindingPath(
binding,
state,
["process", "node:process"],
node,
true,
selected,
)?.length === 0
? { builtinAuthority: "candidate" }
: null;
};
/** Resolve the nearest direct variable/assignment owner before a callable. */
export const outerDataEffectBinding = (
node: t.Node,