feat(javascript): recover runtime semantic effects

This commit is contained in:
morluto
2026-07-24 13:17:38 +00:00
parent 697be942e8
commit 38c883bbc8
28 changed files with 5376 additions and 27 deletions
@@ -0,0 +1,113 @@
import { createJavaScriptSemanticGraphUnknown } from "../domain/javascriptSemanticGraph.js";
import type { JavaScriptSemanticGraphNode } from "../domain/javascriptSemanticGraph.js";
import type { JavaScriptSemanticResourceOperation } from "../domain/javascriptSemanticIr.js";
import {
addSemanticGraphNode,
addSemanticGraphRelation,
addSemanticGraphUnknown,
constructSemanticGraphNode,
} from "./JavaScriptSemanticGraphConstruction.js";
import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js";
import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js";
/** Project built-in resource acquisition and exact local release handles. */
export const projectSemanticResources = (
context: SemanticFlowProjectionContext,
): void => {
const resourceNodes = new Map(
context.ir.resourceOperations.flatMap((operation) => {
if (operation.kind !== "acquire") return [];
const node = addResourceNode(context, operation);
return node === null ? [] : [[operation.resourceId, node] as const];
}),
);
for (const operation of context.ir.resourceOperations)
if (operation.kind === "acquire")
addSemanticGraphRelation(context.state, {
source: callSiteAt(context, operation.location),
target: resourceNodes.get(operation.resourceId),
relation: "acquires",
resolution: "resolved",
});
else projectRelease(context, operation, resourceNodes);
};
const addResourceNode = (
context: SemanticFlowProjectionContext,
operation: JavaScriptSemanticResourceOperation,
): JavaScriptSemanticGraphNode | null =>
addSemanticGraphNode(
context.state,
constructSemanticGraphNode(
context.file,
{
kind: "resource",
roleKey: operation.resourceId,
location: operation.location,
label: operation.method,
functionNodeId:
operation.ownerCallableId === null
? null
: (context.callableNodes.get(operation.ownerCallableId)?.node_id ??
null),
properties: { method: operation.method },
},
context.state,
),
);
const projectRelease = (
context: SemanticFlowProjectionContext,
operation: JavaScriptSemanticResourceOperation,
resourceNodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>,
): void => {
const callSite = callSiteAt(context, operation.location);
const resources = operation.linkedResourceIds.flatMap((identifier) => {
const resource = resourceNodes.get(identifier);
return resource === undefined ? [] : [resource];
});
for (const resource of resources)
addSemanticGraphRelation(context.state, {
source: callSite,
target: resource,
relation: "releases",
resolution:
operation.resolution === "complete" ? "resolved" : "candidate",
});
if (operation.resolution === "complete") return;
addSemanticGraphUnknown(
context.state,
createJavaScriptSemanticGraphUnknown({
node_id: callSite?.node_id ?? null,
family: "resource-lifecycle",
relation_kinds: ["releases"],
reason: "ambiguous-target",
detail: `Static ${operation.method} resource ownership is ambiguous.`,
candidate_node_ids: resources.map(
({ node_id: identifier }) => identifier,
),
evidence: unknownSemanticEvidence(context.file, operation.location),
}),
);
};
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;