diff --git a/tests/acceptance/applications/sourceMapPrefixCli.test.ts b/tests/acceptance/applications/sourceMapPrefixCli.test.ts index 838e469249..af5f8eaec3 100644 --- a/tests/acceptance/applications/sourceMapPrefixCli.test.ts +++ b/tests/acceptance/applications/sourceMapPrefixCli.test.ts @@ -2,6 +2,7 @@ import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; +import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping"; import { expect } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; @@ -85,6 +86,13 @@ cliTest.for(["CLI", "stdio MCP"] as const)( names: [], mappings: "ACAA", }), + "unnamed-empty.js.map": JSON.stringify({ + version: 3, + sources: [null, "original.ts"], + sourcesContent: [null, original], + names: [], + mappings: "ACAA", + }), }; await writeFixtureFiles(root, { ...maps, @@ -92,13 +100,32 @@ cliTest.for(["CLI", "stdio MCP"] as const)( "export const absent = 1;\n//# sourceMappingURL=absent.js.map\n", "mixed.js": "export const answer = 42;\n//# sourceMappingURL=mixed.js.map\n", + "unnamed-empty.js": + "export const answer = 42;\n//# sourceMappingURL=unnamed-empty.js.map\n", }); + for (const [mapPath, expectedSource] of [ + ["absent.js.map", "unembedded.ts"], + ["mixed.js.map", "original.ts"], + ["unnamed-empty.js.map", "original.ts"], + ] as const) + expect( + originalPositionFor(new TraceMap(maps[mapPath], `file:///${mapPath}`), { + line: 1, + column: 0, + }).source, + ).toBe(`file:///${expectedSource}`); const { graph } = await analyzeJavaScriptDirectory(surface, cli, root); const digest = (text: string) => createHash("sha256").update(text).digest("hex"); - for (const [name, mapPath, content] of [ - ["unembedded.ts", "absent.js.map", null], - ["original.ts", "mixed.js.map", original], + expect( + graph.nodes.filter( + (node) => node.identity.strategy === "source-map-original", + ), + ).toHaveLength(3); + for (const [name, mapPath, content, sourceIndex] of [ + ["unembedded.ts", "absent.js.map", null, 0], + ["original.ts", "mixed.js.map", original, 1], + ["original.ts", "unnamed-empty.js.map", original, 1], ] as const) { const mapDigest = digest(maps[mapPath]); expect(graph.nodes).toContainEqual( @@ -117,6 +144,9 @@ cliTest.for(["CLI", "stdio MCP"] as const)( map_path: mapPath, }), properties: expect.objectContaining({ + source_index: sourceIndex, + declaration_index: sourceIndex, + source_name_available: true, content_available: content !== null, }), evidence: expect.objectContaining({ @@ -127,27 +157,77 @@ cliTest.for(["CLI", "stdio MCP"] as const)( }), ); } + for (const [mapPath, content] of [ + ["mixed.js.map", unnamed], + ["unnamed-empty.js.map", null], + ] as const) + expect(graph.nodes).toContainEqual( + expect.objectContaining({ + kind: "unknown", + observations: expect.arrayContaining([ + expect.objectContaining({ + source_map_reference: null, + properties: expect.objectContaining({ + source_index: 0, + declaration_index: 0, + source_name_available: false, + content_available: content !== null, + content_sha256: content === null ? null : digest(content), + }), + evidence: expect.objectContaining({ + artifact: expect.objectContaining({ + sha256: digest(maps[mapPath]), + }), + }), + }), + ]), + }), + ); + expect(graph.coverage.status).toBe("complete"); + for (const [path, text] of Object.entries(maps)) + expect(await readFile(join(root, path), "utf8")).toBe(text); + }, +); + +cliTest.for(["CLI", "stdio MCP"] as const)( + "rejects non-string source-map names without inventing originals through %s", + async (surface, { cli }) => { + const root = await createTestTempDirectory("rea-invalid-map-source-"); + const mapText = JSON.stringify({ + version: 3, + sources: [42, "discarded.ts"], + sourcesContent: [null, "export const answer: number = 42;"], + names: [], + mappings: "ACAA", + }); + await writeFixtureFiles(root, { "invalid.js.map": mapText }); + const { graph } = await analyzeJavaScriptDirectory(surface, cli, root); + expect(graph.coverage.status).toBe("partial"); + expect( + graph.nodes.some( + (node) => node.identity.strategy === "source-map-original", + ), + ).toBe(false); expect(graph.nodes).toContainEqual( expect.objectContaining({ kind: "unknown", observations: expect.arrayContaining([ expect.objectContaining({ properties: expect.objectContaining({ - source_name_available: false, - content_available: true, - content_sha256: digest(unnamed), + operation: "parse-local-source-map", }), evidence: expect.objectContaining({ artifact: expect.objectContaining({ - sha256: digest(maps["mixed.js.map"]), + sha256: createHash("sha256").update(mapText).digest("hex"), }), + limitations: expect.arrayContaining([ + "Source map contains a non-string source name.", + ]), }), }), ]), }), ); - expect(graph.coverage.status).toBe("complete"); - for (const [path, text] of Object.entries(maps)) - expect(await readFile(join(root, path), "utf8")).toBe(text); + expect(await readFile(join(root, "invalid.js.map"), "utf8")).toBe(mapText); }, ); diff --git a/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts b/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts index 7f0a44c1de..aabfdd19b8 100644 --- a/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts +++ b/tests/boundary/filesystem/javascriptArtifactReconstruction.test.ts @@ -136,7 +136,6 @@ it.each([ ["a non-array sourcesContent value", { sourcesContent: "source" }], ["a non-string, non-null source entry", { sourcesContent: [42] }], ["a source-content array with the wrong length", { sourcesContent: [] }], - ["a non-string source name", { sources: [42] }], ])( "reports %s as an invalid local source map", async (_description, content) => {