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, JavaScriptSemanticProjectedResultBinding, } 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 { createSemanticCallableIdStack } from "./javascriptSemanticCallableStack.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 extends JavaScriptSemanticProjectedResultBinding { readonly identifier: t.Identifier; } /** 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[], ): JavaScriptSemanticProjectedResultBinding[] => 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; 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 path = builtinDataEffectCallablePath(callee, state, specifiers); return path?.length === 1 ? (path[0] ?? null) : null; }; /** Recover the complete callable export path using the shared mutation proof. */ export const builtinDataEffectCallablePath = ( callee: t.Node, state: JavaScriptSemanticAnalysisState, specifiers: readonly string[], ): readonly string[] | null => { const expression = unwrapJavaScriptExpression(callee).node; if (expression !== callee) return builtinDataEffectCallablePath(expression, state, specifiers); if (t.isSequenceExpression(expression)) { const final = expression.expressions.at(-1); return final === undefined ? null : builtinDataEffectCallablePath(final, state, specifiers); } const required = semanticRequireOrigin(callee, state); if (required !== undefined) { const origin = builtinNamespaceOrigin(required.origin); return specifiers.some( (specifier) => specifier.replace(/^node:/u, "") === origin.specifier, ) && !requiredModuleChanged( required.origin.specifier, required.origin.importedPath, callee, state, true, ) ? origin.importedPath : null; } if (t.isIdentifier(callee)) { const binding = resolveSemanticBindingState(state, callee, callee.name); const path = builtinBindingPath( binding, state, specifiers, callee, [], true, ); return path; } 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, ) !== null ? [...origin.importedPath, method] : null; } } const source = semanticExpressionModuleOrigin(callee.object, state); if (method === null || source === undefined) return null; const origin = builtinNamespaceOrigin(source); return specifiers.some( (specifier) => specifier.replace(/^node:/u, "") === origin.specifier, ) && !requiredModuleChanged( origin.specifier, [...source.importedPath, method], callee, state, true, callee.object, ) ? [...origin.importedPath, 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 !== null) return [...resolvedPath, 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>; const builtinBindingPath = ( binding: JavaScriptSemanticBindingState | undefined, state: JavaScriptSemanticAnalysisState, specifiers: readonly string[], capture: t.Node, selectedPath: readonly string[] = [], callableValue = false, visited: ReadonlySet = 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, 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>> = { path: new Set(["resolve", "join", "dirname"]), "path/posix": new Set(["resolve", "join", "dirname"]), url: new Set(["fileURLToPath"]), 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; } // 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 = 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 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); const posixReceiver = origin.specifier === "path" && origin.importedPath.length === 1 && 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 ?? "", ]); if ( method === null || !BUILTIN_RECEIVER_METHODS[ posixReceiver ? "path/posix" : 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(), ): 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 => { const callableStack = createSemanticCallableIdStack( context.callableIds, context.callableStack, ); context.ancestors.length = 0; traverseJavaScriptAst(program, { enter: (node, parent) => { callableStack.enter(node, parent); visit(node, parent); context.ancestors.push(node); }, exit: (node) => { context.ancestors.pop(); callableStack.exit(node); }, }); }; /** 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 static string or expression-free template. A template recovered * with an invalid escape has no cooked value; its raw spelling is not a * JavaScript string, so it stays dynamic. */ export const dataEffectLiteralString = ( node: | t.Expression | t.SpreadElement | t.JSXNamespacedName | t.ArgumentPlaceholder | undefined, ): string | null => { if (node === undefined) return null; const literal = readExactJavaScriptLiteral(node); return literal.found && typeof literal.value === "string" ? literal.value : 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;