fix(javascript): accept empty semantic property seeds (#4239)

This commit is contained in:
abdalrhman authored and GitHub committed 2026-10-17 00:10:13 +08:00
1 parent 5b08d04302
commit 3eeada71fd
2 files changed
+77 -2

No files matched your search

@@ -41,7 +41,7 @@ export const javaScriptSemanticQuerySeedSchema = z.discriminatedUnion("kind", [
}),
z.strictObject({
kind: z.literal("property"),
name: z.string().min(1),
name: z.string(),
}),
z.strictObject({
kind: z.literal("endpoint"),
@@ -197,6 +197,78 @@ it("excludes local exports from writes-only traces through CLI and MCP", async (
}
});
it("traces exact empty property names through CLI and MCP", async () => {
const root = await createTestTempDirectory("rea-semantic-empty-property-");
await writeFile(
join(root, "main.js"),
"const routes = {'': 'HOME', x: 'NAMED'}; const root = routes['']; const named = routes.x;\n",
);
const cli = createCli({});
const analyzed = await serveCli(cli, [
"analyze-javascript-application",
root,
"--json",
]);
expect(analyzed.exitCode).toBe(0);
const application = parseEvidence(JSON.parse(analyzed.stdout));
const { semantic_graph: graph } =
javascriptApplicationAnalysisResultSchema.parse(
application.normalized_result,
);
const harness = await createApplicationMcpHarness();
try {
for (const name of ["", "x"]) {
const slots = graph.nodes.filter(
(node) =>
node.kind === "property-slot" && node.properties.name === name,
);
expect(slots).toHaveLength(1);
const slot = slots[0];
if (slot === undefined)
throw new Error("Expected retained property slot");
const exact = await assertSuccessfulTraceParity(
cli,
harness,
application,
{
seed: { kind: "semantic-node", node_id: slot.node_id },
direction: "forward-influence",
},
);
const selected = await assertSuccessfulTraceParity(
cli,
harness,
application,
{
seed: { kind: "property", name },
direction: "forward-influence",
},
);
expect(selected.seed_node_ids).toEqual([slot.node_id]);
expect(selected.nodes).toEqual(exact.nodes);
expect(selected.relations).toEqual(exact.relations);
expect(selected.nodes).toHaveLength(2);
expect(selected.relations).toHaveLength(1);
expect(selected.coverage.status).toBe("partial");
}
const absent = await assertSuccessfulTraceParity(
cli,
harness,
application,
{
seed: { kind: "property", name: "missing" },
direction: "forward-influence",
},
);
expect(absent.seed_node_ids).toEqual([]);
expect(absent.nodes).toEqual([]);
expect(absent.relations).toEqual([]);
expect(absent.coverage.status).toBe("partial");
} finally {
await harness.close();
}
});
const assertSuccessfulTraceParity = async (
cli: ReturnType<typeof createCli>,
harness: Awaited<ReturnType<typeof createApplicationMcpHarness>>,
@@ -213,6 +285,10 @@ const assertSuccessfulTraceParity = async (
JSON.stringify(arguments_),
"--json",
]);
expect(cliResponse.exitCode, cliResponse.stdout).toBe(0);
expect(JSON.parse(cliResponse.stdout), cliResponse.stdout).not.toHaveProperty(
"error",
);
const cliEvidence = parseEvidence(JSON.parse(cliResponse.stdout));
const cliResult = javaScriptSemanticTraceResultSchema.parse(
cliEvidence.normalized_result,
@@ -274,7 +350,6 @@ const assertTraceContextsResolve = (
expect([...referencedIds].toSorted()).toEqual(
[...contexts.keys()].toSorted(),
);
expect(contexts.size).toBeGreaterThan(0);
};
const serveCli = async (