fix(javascript): trace boundary results to assigned bindings (#7912)

This commit is contained in:
morluto authored and GitHub committed 2026-10-29 07:58:57 +08:00
1 parent 2129c17925
commit 794907fa3d
5 files changed
+166 -1

No files matched your search

@@ -665,6 +665,108 @@ it("selects analyzer-produced boundaries by their method field", () => {
).toBe(0);
});
it.each(["Number", "JSON.parse"])(
"connects a resolved %s boundary to its assigned result and later read",
(method) => {
const graph = graphFor(
`function parse(raw) { const n = ${method}(raw); return n; }`,
);
const boundary = graph.nodes.find(
(node) => node.kind === "boundary" && node.label === method,
);
const binding = graph.nodes.find(
(node) => node.kind === "binding" && node.label === "n",
);
const raw = graph.nodes.find(
(node) => node.kind === "parameter" && node.label === "raw",
);
const read = graph.nodes.find((node) =>
node.identity.role_key.endsWith("read:n"),
);
if (!boundary || !binding || !raw || !read)
throw new Error("Expected boundary, input, result binding, and read");
expect(graph.relations).toContainEqual(
expect.objectContaining({
source_node_id: boundary.node_id,
target_node_id: binding.node_id,
relation: "defines",
resolution: "resolved",
}),
);
const query = (
nodeId: string,
direction: "forward-influence" | "backward-provenance",
) =>
queryJavaScriptSemanticGraph(
graph,
javaScriptSemanticQueryInputSchema.parse({
seed: { kind: "semantic-node", node_id: nodeId },
direction,
}),
);
expect(query(boundary.node_id, "forward-influence").nodes).toEqual(
expect.arrayContaining([binding, read]),
);
expect(query(read.node_id, "backward-provenance").nodes).toEqual(
expect.arrayContaining([boundary, raw]),
);
},
);
it.each([
"function parse(raw) { const n = Number(raw) + 1; return n; }",
"function parse(raw) { let n = 1; n += Number(raw); return n; }",
"function parse(raw) { let n = 1; n ||= Number(raw); return n; }",
])(
"does not treat a transformed or conditional boundary as its assigned value: %s",
(source) => {
const graph = graphFor(source);
const boundary = graph.nodes.find((node) => node.kind === "boundary");
const binding = graph.nodes.find(
(node) => node.kind === "binding" && node.label === "n",
);
if (!boundary || !binding) throw new Error("Expected boundary and binding");
expect(graph.relations).not.toContainEqual(
expect.objectContaining({
source_node_id: boundary.node_id,
target_node_id: binding.node_id,
relation: "defines",
}),
);
},
);
it("keeps uncertain parser results as candidates and ignores shadowed coercions", () => {
const graph = graphFor(`
function run(raw, parser, Number) {
const parsed = parser.parse(raw);
const shadowed = Number(raw);
return parsed;
}
`);
const parser = graph.nodes.find(
(node) => node.kind === "boundary" && node.label === "parse",
);
const parsed = graph.nodes.find(
(node) => node.kind === "binding" && node.label === "parsed",
);
if (!parser || !parsed)
throw new Error("Expected parser boundary and binding");
expect(graph.relations).toContainEqual(
expect.objectContaining({
source_node_id: parser.node_id,
target_node_id: parsed.node_id,
relation: "defines",
resolution: "candidate",
}),
);
expect(
graph.nodes.some(
(node) => node.kind === "boundary" && node.label === "Number",
),
).toBe(false);
});
const semanticNodeFor = (roleKey: string) => {
const file: JavaScriptArtifactFile = {
path: "budget.js",