Files
rea/src/application/javascript/JavaScriptSemanticGraphElectronProjection.test.ts
T
morluto f4322b15ed fix(javascript): bound semantic unknown retention at projection
Admit unknowns before canonical construction and provenance interning. Offer independent per-file and application capacities through CLI, Evidence and the owned analysis worker, and reject over-allocation during streamed decoding.

Report examined omitted emission occurrences by family and reason without retaining discarded identities. Preserve omission-free graph identities and distinguish node truncation from overall partial coverage.

Fixes #4797
2026-10-17 21:57:31 +08:00

73 lines
2.6 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "../../domain/javascript/javascriptStaticAnalysis.js";
import { analyzeJavaScriptSemantics } from "../../domain/javascript/javascriptSemanticAnalysis.js";
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
import { projectJavaScriptSemanticFileSteps } from "./JavaScriptSemanticGraphBuilder.js";
const source = `${Array.from({ length: 40 }, (_, i) => `function library${String(i)}(){return ${String(i)}}`).join("\n")}
const e=require("electron");
function register(channel,callback){e.ipcMain.addListener(channel,event=>allowed(event)&&callback(event))}
register("test:copy",event=>event.sender.copy());`;
const file: JavaScriptArtifactFile = {
path: "main.js",
sha256: "a".repeat(64),
container_sha256: "b".repeat(64),
bytes: source.length,
inventory_artifact_id: `art_${"a".repeat(64)}`,
kind: "javascript",
unpacked: false,
text: { included: true, value: source },
};
const facts = analyzeJavaScriptStaticSource(source);
const ir = analyzeJavaScriptSemantics(source);
const project = (budget: number) => {
const steps = projectJavaScriptSemanticFileSteps(
file,
ir,
{ nodes: budget, unknowns: budget },
facts.electron.call_paths,
);
let result = steps.next();
while (!result.done) result = steps.next();
return result.value;
};
it("retains Electron calls before library definitions consume the node budget", () => {
const result = project(20);
expect(result.nodes).toHaveLength(20);
expect(result.nodesTruncated).toBe(true);
const terminal = result.nodes.find(
(node) => node.label === "ipc-event.sender.copy",
);
expect(terminal).toBeDefined();
expect(
result.relations.some(
(r) =>
r.target_node_id === terminal?.node_id && r.resolution === "candidate",
),
).toBe(true);
expect(
result.nodes.some(
(node) =>
node.properties.entry === "test:copy" &&
Array.isArray(node.properties.conditions) &&
node.properties.conditions.length === 1,
),
).toBe(true);
});
it("reports truncation and keeps referential integrity even within a boundary path", () => {
const result = project(3);
expect(result.nodes).toHaveLength(3);
expect(result.nodesTruncated).toBe(true);
expect(
result.nodes.some((node) => node.label === "ipc-event.sender.copy"),
).toBe(false);
const retained = new Set(result.nodes.map((node) => node.node_id));
expect(
result.relations.every(
(r) => retained.has(r.source_node_id) && retained.has(r.target_node_id),
),
).toBe(true);
});