diff --git a/src/application/JavaScriptSemanticGraphAsyncProjection.ts b/src/application/JavaScriptSemanticGraphAsyncProjection.ts index 3db4e44042..34776b55c2 100644 --- a/src/application/JavaScriptSemanticGraphAsyncProjection.ts +++ b/src/application/JavaScriptSemanticGraphAsyncProjection.ts @@ -11,7 +11,10 @@ import { constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js"; import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js"; -import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js"; +import { + semanticCallSiteAt, + type SemanticFlowProjectionContext, +} from "./JavaScriptSemanticGraphFlowProjection.js"; /** Project literal EventEmitter registrations, removals, and dispatches. */ export const projectSemanticEvents = ( @@ -194,7 +197,7 @@ const projectTimerSchedule = ( timerNodes: ReadonlyMap, ): void => { addSemanticGraphRelation(context.state, { - source: callSiteAt(context, operation.location), + source: semanticCallSiteAt(context, operation.location), target: timerNodes.get(operation.timerId), relation: "schedules-timer", resolution: "resolved", @@ -206,7 +209,7 @@ const projectTimerCancellation = ( operation: JavaScriptSemanticTimerOperation, timerNodes: ReadonlyMap, ): void => { - const callSite = callSiteAt(context, operation.location); + const callSite = semanticCallSiteAt(context, operation.location); const target = operation.linkedTimerId === null ? undefined @@ -231,26 +234,3 @@ const projectTimerCancellation = ( }), ); }; - -const callSiteAt = ( - context: SemanticFlowProjectionContext, - location: JavaScriptTimerLocation, -): JavaScriptSemanticGraphNode | undefined => { - const call = context.ir.callSites.find(({ location: candidate }) => - rangesEqual(candidate, location), - ); - return call === undefined - ? undefined - : context.callSiteNodes.get(call.callSiteId); -}; - -type JavaScriptTimerLocation = JavaScriptSemanticTimerOperation["location"]; - -const rangesEqual = ( - left: JavaScriptTimerLocation, - right: JavaScriptTimerLocation, -): boolean => - left.start.line === right.start.line && - left.start.column === right.start.column && - left.end.line === right.end.line && - left.end.column === right.end.column; diff --git a/src/application/JavaScriptSemanticGraphChildProcessProjection.ts b/src/application/JavaScriptSemanticGraphChildProcessProjection.ts index 9c9e79686e..d4e08b5ec1 100644 --- a/src/application/JavaScriptSemanticGraphChildProcessProjection.ts +++ b/src/application/JavaScriptSemanticGraphChildProcessProjection.ts @@ -11,7 +11,10 @@ import { constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js"; import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js"; -import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js"; +import { + semanticCallSiteAt, + type SemanticFlowProjectionContext, +} from "./JavaScriptSemanticGraphFlowProjection.js"; interface ChildUnknownInput { readonly context: SemanticFlowProjectionContext; @@ -81,7 +84,7 @@ const projectSpawn = ( spawn: JavaScriptSemanticChildProcessSpawn, child: JavaScriptSemanticGraphNode, ): void => { - const callSite = callSiteAt(context, spawn.location); + const callSite = semanticCallSiteAt(context, spawn.location); addSemanticGraphRelation(context.state, { source: callSite, target: child, @@ -252,24 +255,3 @@ const addChildUnknown = (input: ChildUnknownInput): void => { }), ); }; - -const callSiteAt = ( - context: SemanticFlowProjectionContext, - location: JavaScriptSemanticChildProcessSpawn["location"], -): JavaScriptSemanticGraphNode | undefined => { - const call = context.ir.callSites.find(({ location: candidate }) => - rangesEqual(candidate, location), - ); - return call === undefined - ? undefined - : context.callSiteNodes.get(call.callSiteId); -}; - -const rangesEqual = ( - left: JavaScriptSemanticChildProcessSpawn["location"], - right: JavaScriptSemanticChildProcessSpawn["location"], -): boolean => - left.start.line === right.start.line && - left.start.column === right.start.column && - left.end.line === right.end.line && - left.end.column === right.end.column; diff --git a/src/application/JavaScriptSemanticGraphDataProjection.ts b/src/application/JavaScriptSemanticGraphDataProjection.ts index 12668a5e51..e56f1e362e 100644 --- a/src/application/JavaScriptSemanticGraphDataProjection.ts +++ b/src/application/JavaScriptSemanticGraphDataProjection.ts @@ -12,7 +12,10 @@ import { constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js"; import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js"; -import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js"; +import { + semanticCallSiteAt, + type SemanticFlowProjectionContext, +} from "./JavaScriptSemanticGraphFlowProjection.js"; /** Project environment, argv, file, default, and precedence candidates. */ export const projectSemanticConfiguration = ( @@ -170,7 +173,7 @@ const projectRequestConstruction = ( operation: JavaScriptSemanticRequestOperation, request: JavaScriptSemanticGraphNode, ): void => { - const callSite = callSiteAt(context, operation.location); + const callSite = semanticCallSiteAt(context, operation.location); addSemanticGraphRelation(context.state, { source: callSite, target: request, @@ -197,7 +200,7 @@ const projectResponseConsumer = ( ): void => { addSemanticGraphRelation(context.state, { source: response, - target: callSiteAt(context, operation.location), + target: semanticCallSiteAt(context, operation.location), relation: "consumed-by", resolution: operation.resolution === "complete" ? "resolved" : "candidate", properties: { method: operation.method }, @@ -328,24 +331,3 @@ const addDataUnknown = (input: DataUnknownInput): void => { }), ); }; - -const callSiteAt = ( - context: SemanticFlowProjectionContext, - location: JavaScriptSemanticBoundaryOperation["location"], -): JavaScriptSemanticGraphNode | undefined => { - const call = context.ir.callSites.find(({ location: candidate }) => - rangesEqual(candidate, location), - ); - return call === undefined - ? undefined - : context.callSiteNodes.get(call.callSiteId); -}; - -const rangesEqual = ( - left: JavaScriptSemanticBoundaryOperation["location"], - right: JavaScriptSemanticBoundaryOperation["location"], -): boolean => - left.start.line === right.start.line && - left.start.column === right.start.column && - left.end.line === right.end.line && - left.end.column === right.end.column; diff --git a/src/application/JavaScriptSemanticGraphFlowProjection.ts b/src/application/JavaScriptSemanticGraphFlowProjection.ts index 71fd0c54d9..51f95d8004 100644 --- a/src/application/JavaScriptSemanticGraphFlowProjection.ts +++ b/src/application/JavaScriptSemanticGraphFlowProjection.ts @@ -1,6 +1,7 @@ import { createJavaScriptSemanticGraphUnknown } from "../domain/javascriptSemanticGraph.js"; import type { JavaScriptSemanticGraphNode } from "../domain/javascriptSemanticGraph.js"; import type { JavaScriptSemanticIr } from "../domain/javascriptSemanticIr.js"; +import type { JavaScriptSourceRange } from "../domain/javascriptStaticAnalysisTypes.js"; import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js"; import type { JavaScriptArtifactFile } from "./JavaScriptArtifactFiles.js"; import { @@ -14,7 +15,10 @@ import { inferredSemanticEvidenceAt, unknownSemanticEvidence, } from "./JavaScriptSemanticGraphEvidence.js"; -import { semanticNodesWithinRange } from "./JavaScriptSemanticGraphProjection.js"; +import { + semanticNodesWithinRange, + semanticRangesEqual, +} from "./JavaScriptSemanticGraphProjection.js"; /** File-local graph state needed by return, capture, and frontier projection. */ export interface SemanticFlowProjectionContext { @@ -29,6 +33,19 @@ export interface SemanticFlowProjectionContext { readonly referenceNodes: readonly JavaScriptSemanticGraphNode[]; } +/** Find the retained call-site node at one exact semantic source range. */ +export const semanticCallSiteAt = ( + context: SemanticFlowProjectionContext, + location: JavaScriptSourceRange, +): JavaScriptSemanticGraphNode | undefined => { + const call = context.ir.callSites.find(({ location: candidate }) => + semanticRangesEqual(candidate, location), + ); + return call === undefined + ? undefined + : context.callSiteNodes.get(call.callSiteId); +}; + /** Project explicit static Promise/task ownership without runtime claims. */ export const projectSemanticPromises = ( context: SemanticFlowProjectionContext, @@ -89,14 +106,8 @@ const projectPromiseCreation = ( promise: JavaScriptSemanticGraphNode, ): void => { if (operation.kind === "awaited-expression") return; - const call = context.ir.callSites.find(({ location }) => - rangesEqual(location, operation.location), - ); addSemanticGraphRelation(context.state, { - source: - call === undefined - ? undefined - : context.callSiteNodes.get(call.callSiteId), + source: semanticCallSiteAt(context, operation.location), target: promise, relation: "creates-promise", resolution: @@ -220,7 +231,7 @@ export const projectSemanticReturnValues = ( relation: "aliases", resolution: site.identityReferenceLocation !== null && - rangesEqual( + semanticRangesEqual( reference.identity.source_range, site.identityReferenceLocation, ) @@ -230,17 +241,6 @@ export const projectSemanticReturnValues = ( } }; -const rangesEqual = ( - left: JavaScriptSemanticGraphNode["identity"]["source_range"], - right: JavaScriptSemanticGraphNode["identity"]["source_range"], -): boolean => - left !== null && - right !== null && - left.start.line === right.start.line && - left.start.column === right.start.column && - left.end.line === right.end.line && - left.end.column === right.end.column; - /** Link each captured binding to the callable at the exact reference site. */ export const projectSemanticClosureCaptures = ( context: SemanticFlowProjectionContext, diff --git a/src/application/JavaScriptSemanticGraphResourceProjection.ts b/src/application/JavaScriptSemanticGraphResourceProjection.ts index e869cdd881..1483b99058 100644 --- a/src/application/JavaScriptSemanticGraphResourceProjection.ts +++ b/src/application/JavaScriptSemanticGraphResourceProjection.ts @@ -8,7 +8,10 @@ import { constructSemanticGraphNode, } from "./JavaScriptSemanticGraphConstruction.js"; import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js"; -import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js"; +import { + semanticCallSiteAt, + type SemanticFlowProjectionContext, +} from "./JavaScriptSemanticGraphFlowProjection.js"; /** Project built-in resource acquisition and exact local release handles. */ export const projectSemanticResources = ( @@ -24,7 +27,7 @@ export const projectSemanticResources = ( for (const operation of context.ir.resourceOperations) if (operation.kind === "acquire") addSemanticGraphRelation(context.state, { - source: callSiteAt(context, operation.location), + source: semanticCallSiteAt(context, operation.location), target: resourceNodes.get(operation.resourceId), relation: "acquires", resolution: "resolved", @@ -61,7 +64,7 @@ const projectRelease = ( operation: JavaScriptSemanticResourceOperation, resourceNodes: ReadonlyMap, ): void => { - const callSite = callSiteAt(context, operation.location); + const callSite = semanticCallSiteAt(context, operation.location); const resources = operation.linkedResourceIds.flatMap((identifier) => { const resource = resourceNodes.get(identifier); return resource === undefined ? [] : [resource]; @@ -90,24 +93,3 @@ const projectRelease = ( }), ); }; - -const callSiteAt = ( - context: SemanticFlowProjectionContext, - location: JavaScriptSemanticResourceOperation["location"], -): JavaScriptSemanticGraphNode | undefined => { - const call = context.ir.callSites.find(({ location: candidate }) => - rangesEqual(candidate, location), - ); - return call === undefined - ? undefined - : context.callSiteNodes.get(call.callSiteId); -}; - -const rangesEqual = ( - left: JavaScriptSemanticResourceOperation["location"], - right: JavaScriptSemanticResourceOperation["location"], -): boolean => - left.start.line === right.start.line && - left.start.column === right.start.column && - left.end.line === right.end.line && - left.end.column === right.end.column;