From 3e19875c7fa9c61cff655bbe6046ddc4ab558533 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:39:37 +0800 Subject: [PATCH] fix(javascript): retain unresolved value frontiers in provenance queries --- .../JavaScriptApplicationSignedZero.test.ts | 78 +++++++++++++++---- .../JavaScriptSemanticGraphBuilder.test.ts | 3 +- .../JavaScriptSemanticGraphValueProjection.ts | 16 ++-- .../javascriptSemanticGraphSchemas.ts | 1 + 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/application/javascript/JavaScriptApplicationSignedZero.test.ts b/src/application/javascript/JavaScriptApplicationSignedZero.test.ts index b1f4df0ba4..a36c1fdab4 100644 --- a/src/application/javascript/JavaScriptApplicationSignedZero.test.ts +++ b/src/application/javascript/JavaScriptApplicationSignedZero.test.ts @@ -5,22 +5,66 @@ import { expect, it } from "vitest"; import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js"; import { javascriptApplicationAnalysisResultSchema } from "../../domain/javascript/javascriptApplicationAnalysis.js"; +import { javaScriptSemanticQueryResultSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js"; +import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js"; import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js"; -it("does not expose a resolved positive-zero literal for a negative-zero export", async () => { - const root = await createTestTempDirectory("rea-signed-zero-"); - await writeFile(join(root, "app.js"), "export const value = -0;\n"); - const result = await analyzeJavaScriptApplication({ - input_path: root, - format: "directory", - }); - if (!result.ok) throw result.error; - const analysis = javascriptApplicationAnalysisResultSchema.parse( - result.value.normalized_result, - ); - expect( - analysis.semantic_graph.nodes.filter( - ({ kind, properties }) => kind === "literal" && properties.value === 0, - ), - ).toEqual([]); -}); +it.each([ + { + source: "export const value = -0;\n", + target: { kind: "binding", label: "value" }, + }, + { + source: "export const root = { value: -0 };\n", + target: { kind: "property-slot", propertyPointer: "/value" }, + }, +])( + "keeps negative-zero value unknown in an exported query (%#)", + async ({ source, target }) => { + const root = await createTestTempDirectory("rea-signed-zero-"); + await writeFile(join(root, "app.js"), source); + const result = await analyzeJavaScriptApplication({ + input_path: root, + format: "directory", + }); + if (!result.ok) throw result.error; + const analysis = javascriptApplicationAnalysisResultSchema.parse( + result.value.normalized_result, + ); + const node = + target.kind === "binding" + ? analysis.semantic_graph.nodes.find( + ({ kind, label }) => kind === "binding" && label === target.label, + ) + : analysis.semantic_graph.nodes.find( + ({ kind, properties }) => + kind === "property-slot" && + properties.property_pointer === target.propertyPointer, + ); + if (node === undefined) + throw new Error("Expected the selected semantic node"); + const query = queryJavaScriptSemanticGraph(analysis.semantic_graph, { + seed: { kind: "semantic-node", node_id: node.node_id }, + direction: "backward-provenance", + allowed_relations: ["defines"], + }); + const transportedQuery = javaScriptSemanticQueryResultSchema.parse( + JSON.parse(JSON.stringify(query)), + ); + expect(transportedQuery.nodes.some(({ kind }) => kind === "literal")).toBe( + false, + ); + expect(transportedQuery.coverage.status).toBe("partial"); + expect(transportedQuery.unknowns).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + reason: "unknown-value", + node_id: node.node_id, + detail: expect.stringContaining( + "Negative zero cannot be preserved by JSON primitive evidence.", + ), + }), + ]), + ); + }, +); diff --git a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts index af74de6163..6dd3f85764 100644 --- a/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts +++ b/src/application/javascript/JavaScriptSemanticGraphBuilder.test.ts @@ -145,7 +145,8 @@ it("retains resource-limit reasons at nested object property slots", () => { ]); expect(graph.unknowns).toContainEqual( expect.objectContaining({ - family: "object-flow", + family: "data-flow", + relation_kinds: ["defines"], reason: "resource-limit", detail: expect.stringMatching( /Unknown value at property:\/nested\/value/u, diff --git a/src/application/javascript/JavaScriptSemanticGraphValueProjection.ts b/src/application/javascript/JavaScriptSemanticGraphValueProjection.ts index cb2596b288..bcc9db2941 100644 --- a/src/application/javascript/JavaScriptSemanticGraphValueProjection.ts +++ b/src/application/javascript/JavaScriptSemanticGraphValueProjection.ts @@ -111,17 +111,23 @@ const projectValue = (input: ValueProjectionInput): void => { ), }), ); - } else if (value.status === "unknown" && value.resourceLimit !== undefined) { + } else if ( + value.status === "unknown" || + value.status === "ambiguous" || + value.status === "cycle" + ) { const location = binding.definitions[0]?.location ?? null; const evidence = observedSemanticEvidence(context.file, location); - const isPropertyValue = role.startsWith("property:"); addSemanticGraphUnknown( context.state, createJavaScriptSemanticGraphUnknown({ node_id: target.node_id, - family: isPropertyValue ? "object-flow" : "data-flow", - relation_kinds: [isPropertyValue ? "writes-property" : "defines"], - reason: "resource-limit", + family: "data-flow", + relation_kinds: ["defines"], + reason: + value.resourceLimit === undefined + ? "unknown-value" + : "resource-limit", detail: `${value.reason} Unknown value at ${role}.`, candidate_node_ids: [target.node_id], evidence: { diff --git a/src/domain/javascript/javascriptSemanticGraphSchemas.ts b/src/domain/javascript/javascriptSemanticGraphSchemas.ts index c5374ebe4a..64e7041984 100644 --- a/src/domain/javascript/javascriptSemanticGraphSchemas.ts +++ b/src/domain/javascript/javascriptSemanticGraphSchemas.ts @@ -251,6 +251,7 @@ export const JAVASCRIPT_SEMANTIC_UNKNOWN_REASONS = [ "incomplete-module", "missing-source", "resource-limit", + "unknown-value", "unsupported-syntax", ] as const;