Retain directive literal values, including escaped strict-mode lookalikes, in semantic function fingerprints. Verify normalized duplicates and distinct directives through CLI/MCP. Co-authored-by: To3akaRin <131595336+To3akaRin@users.noreply.github.com>
128 lines
4.9 KiB
TypeScript
128 lines
4.9 KiB
TypeScript
import { mkdir, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
|
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
|
import { javaScriptSemanticTraceResultSchema } from "../../../src/domain/javascript/javascriptSemanticTraceSchemas.js";
|
|
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"function fingerprint seeds retain directive values and normalized duplicates through CLI and MCP",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-function-fingerprints-");
|
|
const application = join(root, "application");
|
|
await mkdir(application);
|
|
await writeFile(
|
|
join(application, "app.cjs"),
|
|
String.raw`
|
|
throw new Error("This analysis fixture must not execute");
|
|
function strict(value) { "use strict"; return value; }
|
|
function ordinary(value) { "ordinary"; return value; }
|
|
function asm(value) { "use asm"; return value; }
|
|
function alpha(value) { "alpha"; return value; }
|
|
function beta(value) { "beta"; return value; }
|
|
function escaped(value) { "use\x20strict"; return value; }
|
|
function renamed(input) { 'use strict'; return input; }
|
|
`,
|
|
);
|
|
const analyzed = await cli.run({
|
|
arguments: [
|
|
"analyze-javascript-application",
|
|
application,
|
|
"--artifact-format",
|
|
"directory",
|
|
"--json",
|
|
],
|
|
environment: { REA_LOG_LEVEL: "silent" },
|
|
});
|
|
expect(analyzed.exitCode, analyzed.stderr).toBe(0);
|
|
const evidence = toolContract(
|
|
"analyze_javascript_application",
|
|
).outputSchema.parse(analyzed.json);
|
|
const { semantic_graph: graph } =
|
|
javascriptApplicationAnalysisResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
const { client, call } = await connectLocalToolsMcp();
|
|
const response = await call("analyze_javascript_application", {
|
|
input_path: application,
|
|
format: "directory",
|
|
});
|
|
expect(response.isError).not.toBe(true);
|
|
const retained = toolContract(
|
|
"analyze_javascript_application",
|
|
).outputSchema.parse(response.structuredContent);
|
|
expect(retained.evidence_id).toBe(evidence.evidence_id);
|
|
const fingerprint = (name: string) => {
|
|
const node = graph.nodes.find(
|
|
(candidate) =>
|
|
candidate.kind === "function" && candidate.properties.name === name,
|
|
);
|
|
if (node === undefined) throw new Error(`Missing function ${name}`);
|
|
const result = graph.fingerprints.find(
|
|
({ function_node_id }) => function_node_id === node.node_id,
|
|
);
|
|
if (result === undefined) throw new Error(`Missing fingerprint ${name}`);
|
|
expect(result.status).toBe("complete");
|
|
return result;
|
|
};
|
|
for (const [selected, expectedNames] of [
|
|
["strict", ["strict", "renamed"]],
|
|
["ordinary", ["ordinary"]],
|
|
["asm", ["asm"]],
|
|
["alpha", ["alpha"]],
|
|
["beta", ["beta"]],
|
|
["escaped", ["escaped"]],
|
|
] as const) {
|
|
const query = {
|
|
seed: {
|
|
kind: "function",
|
|
fingerprint_sha256: fingerprint(selected).fingerprint_sha256,
|
|
},
|
|
direction: "forward-influence",
|
|
};
|
|
const traced = await call("trace_javascript_semantics", {
|
|
application: {
|
|
kind: "retained-evidence",
|
|
evidence_id: retained.evidence_id,
|
|
},
|
|
query,
|
|
});
|
|
expect(traced.isError, JSON.stringify(traced)).not.toBe(true);
|
|
const traceEvidence = toolContract(
|
|
"trace_javascript_semantics",
|
|
).outputSchema.parse(traced.structuredContent);
|
|
const trace = javaScriptSemanticTraceResultSchema.parse(
|
|
traceEvidence.normalized_result,
|
|
);
|
|
expect(trace.seed_node_ids).toEqual(
|
|
expectedNames.map((name) => fingerprint(name).function_node_id).sort(),
|
|
);
|
|
expect(trace.status).toBe(
|
|
expectedNames.length > 1 ? "ambiguous" : "found",
|
|
);
|
|
expect(trace.source_graph_id).toBe(graph.graph_id);
|
|
if (selected === "strict") {
|
|
const requestPath = join(root, "trace-request.json");
|
|
await writeFile(
|
|
requestPath,
|
|
JSON.stringify({ application: evidence, query }),
|
|
);
|
|
const cliTrace = await cli.run({
|
|
arguments: ["trace-javascript-semantics", requestPath, "--json"],
|
|
environment: { REA_LOG_LEVEL: "silent" },
|
|
});
|
|
expect(cliTrace.exitCode, cliTrace.stderr).toBe(0);
|
|
const cliEvidence = toolContract(
|
|
"trace_javascript_semantics",
|
|
).outputSchema.parse(cliTrace.json);
|
|
expect(cliEvidence.evidence_id).toBe(traceEvidence.evidence_id);
|
|
}
|
|
}
|
|
await client.ping();
|
|
},
|
|
);
|