fix(javascript): keep exact empty values in graph identities

#839 displayed empty artifact-local keys and source-map original names as
`""` inside node identities, so `fetch("")` and `fetch('""')` (or
`sources: ["", "\"\""]`) produced the same node ID and merged two
distinct facts. Function nodes likewise exposed only a display label, so
`['']()` and `['""']()` were indistinguishable.

Admit the exact empty string in `artifact-local-key.key` and
`source-map-original.original_source`, keep the `""` substitution for
labels only, and record each function node's exact `name` in its
properties. Regenerate the catalog digests for the relaxed output schema.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iwakurakaoru0822andClaude Opus 5.5 committed 2026-10-06 21:00:57 -07:00
1 parent e7cbfe04fd
commit 63012eeb60
9 files changed
+58 -41

No files matched your search

@@ -52,7 +52,7 @@ it.each([
it("displays empty literals as nonempty text while retaining exact values", async () => {
const output = await analyzeSource(
"fetch(''); fetch('/api'); process.on('', () => 1);",
"fetch(''); fetch('\"\"'); fetch('/api'); process.on('', () => 1);",
);
const endpoints = output.graph.nodes
.filter(({ kind }) => kind === "endpoint")
@@ -61,12 +61,16 @@ it("displays empty literals as nonempty text while retaining exact values", asyn
labels: observations.map(({ label }) => label),
values: observations.map(({ properties }) => properties.value),
}));
// The empty endpoint and a literal two-quote endpoint share display text
// but remain distinct nodes with exact identities.
expect(endpoints).toEqual(
expect.arrayContaining([
{ key: '""', labels: ['""'], values: [""] },
{ key: "", labels: ['""'], values: [""] },
{ key: '""', labels: ['""'], values: ['""'] },
{ key: "/api", labels: ["/api"], values: ["/api"] },
]),
);
expect(endpoints).toHaveLength(3);
expect(
output.semantic_graph?.nodes
.filter(({ kind }) => kind === "event")
@@ -101,5 +105,26 @@ it.each([
: null,
sources: observations.map(({ properties }) => properties.source),
}));
expect(originals).toEqual([{ originalSource: '""', sources: [""] }]);
expect(originals).toEqual([{ originalSource: "", sources: [""] }]);
});
it("keeps empty and two-quote source-map names distinct", async () => {
const output = await analyzeSource("//# sourceMappingURL=app.js.map", {
"app.js.map": JSON.stringify({
version: 3,
sources: ["", '""'],
names: [],
mappings: "AAAA,CCAA",
}),
});
expect(
output.graph.nodes
.filter(({ kind }) => kind === "source-module")
.map(({ identity }) =>
identity.strategy === "source-map-original"
? identity.original_source
: null,
)
.sort(),
).toEqual(["", '""']);
});