77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
|
|
import type { JavaScriptSemanticGraphNode } from "../../domain/javascriptSemanticGraph.js";
|
|
import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES } from "../../domain/javascriptSemanticGraphSchemas.js";
|
|
import type { JavaScriptSemanticIr } from "../../domain/javascriptSemanticIr.js";
|
|
import type { JavaScriptSourceRange } from "../../domain/javascriptStaticAnalysisTypes.js";
|
|
import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
|
|
|
|
/** Find the innermost callable that contains one exact source range. */
|
|
export const owningSemanticCallableNode = (
|
|
location: JavaScriptSourceRange | null,
|
|
ir: JavaScriptSemanticIr,
|
|
nodes: ReadonlyMap<string, JavaScriptSemanticGraphNode>,
|
|
): JavaScriptSemanticGraphNode | undefined => {
|
|
if (location === null) return undefined;
|
|
return ir.callables
|
|
.filter((callable) => contains(callable.location, location))
|
|
.sort((left, right) =>
|
|
compareCodePoints(
|
|
positionKey(right.location),
|
|
positionKey(left.location),
|
|
),
|
|
)
|
|
.map(({ callableId }) => nodes.get(callableId))
|
|
.find((node) => node !== undefined);
|
|
};
|
|
|
|
/** Find retained semantic nodes whose exact ranges are within one expression. */
|
|
export const semanticNodesWithinRange = (
|
|
nodes: readonly JavaScriptSemanticGraphNode[],
|
|
range: JavaScriptSourceRange,
|
|
): JavaScriptSemanticGraphNode[] =>
|
|
nodes.filter(
|
|
(node) =>
|
|
node.identity.source_range !== null &&
|
|
contains(range, node.identity.source_range),
|
|
);
|
|
|
|
/** Report extractor support without treating missing families as absence. */
|
|
export const semanticFamilyStatus = (
|
|
family: (typeof JAVASCRIPT_SEMANTIC_RELATION_FAMILIES)[number],
|
|
analysis: JavaScriptArtifactAnalysis,
|
|
): "complete" | "partial" | "unknown" | "unsupported" => {
|
|
if (
|
|
![
|
|
"call-flow",
|
|
"boundary",
|
|
"child-process",
|
|
"closure",
|
|
"configuration",
|
|
"data-flow",
|
|
"event",
|
|
"object-flow",
|
|
"promise-ownership",
|
|
"request",
|
|
"resource-lifecycle",
|
|
"timer",
|
|
].includes(family)
|
|
)
|
|
return "unsupported";
|
|
return analysis.truncated_scopes === 0 ? "partial" : "unknown";
|
|
};
|
|
|
|
const contains = (
|
|
outer: JavaScriptSourceRange,
|
|
inner: JavaScriptSourceRange,
|
|
): boolean =>
|
|
pointCompare(outer.start, inner.start) <= 0 &&
|
|
pointCompare(outer.end, inner.end) >= 0;
|
|
|
|
const pointCompare = (
|
|
left: JavaScriptSourceRange["start"],
|
|
right: JavaScriptSourceRange["start"],
|
|
): number => left.line - right.line || left.column - right.column;
|
|
|
|
const positionKey = (range: JavaScriptSourceRange): string =>
|
|
`${String(range.start.line).padStart(12, "0")}:${String(range.start.column).padStart(12, "0")}`;
|