Files
rea/tests/acceptance/applications/sourceMapPrefixCli.test.ts
morluto 2b2e362a00 fix(javascript): retain partial indexed source map facts
Share section classification across declaration analysis and mapping parsing. Preserve external URLs, malformed section reasons, section paths and safe sibling declarations without fetching nested maps.

Refs #5466. All 50 focused tests passed, including real CLI and stdio MCP regressions; typecheck and lint passed.
2026-10-20 22:18:23 +08:00

420 lines
15 KiB
TypeScript

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";
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
cliTest.for(["CLI", "stdio MCP"] as const)(
"keeps missing source-map mappings partial while retaining explicit declarations through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-source-map-mappings-");
const original = "export const answer: number = 42;";
const cases = [
{
directory: "missing",
map: {
version: 3,
sources: ["original.ts"],
sourcesContent: [original],
names: [],
},
expectedCoverage: "partial",
limitation: "Source map has no mappings string.",
},
{
directory: "empty",
map: {
version: 3,
sources: ["original.ts"],
sourcesContent: [original],
names: [],
mappings: "",
},
expectedCoverage: "complete",
limitation: null,
},
{
directory: "mapped",
map: {
version: 3,
sources: ["original.ts"],
sourcesContent: [original],
names: [],
mappings: "AAAA",
},
expectedCoverage: "complete",
limitation: null,
},
] as const;
for (const { directory, map, expectedCoverage, limitation } of cases) {
const app = join(root, directory);
const mapText = JSON.stringify(map);
await writeFixtureFiles(app, { "app.js.map": mapText });
const { graph } = await analyzeJavaScriptDirectory(surface, cli, app);
const mapDigest = createHash("sha256").update(mapText).digest("hex");
const source = graph.nodes.find(
(node) =>
node.kind === "source-module" &&
node.identity.strategy === "source-map-original" &&
node.identity.source_map_sha256 === mapDigest,
);
expect(graph.coverage.status).toBe(expectedCoverage);
expect(source).toMatchObject({
identity: {
strategy: "source-map-original",
original_source: "original.ts",
source_sha256: createHash("sha256").update(original).digest("hex"),
},
observations: [
expect.objectContaining({
evidence: expect.objectContaining({
artifact: expect.objectContaining({ sha256: mapDigest }),
limitations: limitation === null ? [] : [limitation],
}),
}),
],
});
if (limitation !== null)
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "unknown",
observations: expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
operation: "parse-local-source-map",
}),
evidence: expect.objectContaining({
artifact: expect.objectContaining({ sha256: mapDigest }),
limitations: [limitation],
}),
}),
]),
}),
);
}
},
);
cliTest.for(["CLI", "stdio MCP"] as const)(
"recovers retained prefixed source-map originals and byte identity through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-retained-map-prefix-");
const original = "export const answer: number = 42;";
const mapText = `\uFEFF)]}'retained response\r\n${JSON.stringify({
version: 3,
sources: ["original.ts"],
sourcesContent: [original],
names: [],
mappings: "AAAA",
})}`;
const mapPath = join(root, "bundle.js.map");
await writeFile(
join(root, "bundle.js"),
"export const answer = 42;\n//# sourceMappingURL=bundle.js.map\n",
);
await writeFile(mapPath, mapText);
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
const mapDigest = createHash("sha256").update(mapText).digest("hex");
const originalDigest = createHash("sha256").update(original).digest("hex");
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "source-module",
identity: expect.objectContaining({
strategy: "source-map-original",
source_map_sha256: mapDigest,
original_source: "original.ts",
source_sha256: originalDigest,
}),
observations: expect.arrayContaining([
expect.objectContaining({
source_map_reference: expect.objectContaining({
source_name: "original.ts",
map_path: "bundle.js.map",
}),
properties: expect.objectContaining({
source_index: 0,
declaration_index: 0,
source_name_available: true,
content_available: true,
content_sha256: originalDigest,
}),
evidence: expect.objectContaining({
artifact: expect.objectContaining({ sha256: mapDigest }),
}),
}),
]),
}),
);
expect(graph.coverage.status).toBe("complete");
expect(await readFile(mapPath, "utf8")).toBe(mapText);
},
);
cliTest.for(["CLI", "stdio MCP"] as const)(
"retains nullable source-map declarations and aligned content through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-nullable-map-");
const original = "export const answer: number = 42;";
const unnamed = "retained source text without a declared name";
const maps = {
"absent.js.map": JSON.stringify({
version: 3,
sources: ["unembedded.ts"],
sourcesContent: null,
names: [],
mappings: "AAAA",
}),
"mixed.js.map": JSON.stringify({
version: 3,
sources: [null, "original.ts"],
sourcesContent: [unnamed, original],
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,
"absent.js":
"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");
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(
expect.objectContaining({
kind: "source-module",
identity: expect.objectContaining({
strategy: "source-map-original",
source_map_sha256: mapDigest,
original_source: name,
source_sha256: content === null ? null : digest(content),
}),
observations: expect.arrayContaining([
expect.objectContaining({
source_map_reference: expect.objectContaining({
source_name: name,
map_path: mapPath,
}),
properties: expect.objectContaining({
source_index: sourceIndex,
declaration_index: sourceIndex,
source_name_available: true,
content_available: content !== null,
}),
evidence: expect.objectContaining({
artifact: expect.objectContaining({ sha256: mapDigest }),
}),
}),
]),
}),
);
}
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)(
"retains valid declarations beside non-string source-map names 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");
const mapDigest = createHash("sha256").update(mapText).digest("hex");
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "source-module",
identity: expect.objectContaining({
strategy: "source-map-original",
source_map_sha256: mapDigest,
original_source: "discarded.ts",
source_sha256: createHash("sha256")
.update("export const answer: number = 42;")
.digest("hex"),
}),
observations: expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
section_path: [],
source_index: 1,
declaration_index: 0,
}),
}),
]),
}),
);
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "unknown",
observations: expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
operation: "parse-local-source-map",
}),
evidence: expect.objectContaining({
artifact: expect.objectContaining({
sha256: mapDigest,
}),
limitations: expect.arrayContaining([
"Source map contains a non-string source name at index 0.",
]),
}),
}),
]),
}),
);
expect(await readFile(join(root, "invalid.js.map"), "utf8")).toBe(mapText);
},
);
cliTest.for(["CLI", "stdio MCP"] as const)(
"keeps external indexed sections unknown while retaining embedded siblings through %s",
async (surface, { cli }) => {
const root = await createTestTempDirectory("rea-external-indexed-map-");
const url = "https://app.test/maps/part.map?revision=7";
const mapText = JSON.stringify({
version: 3,
sections: [
{
offset: { line: 0, column: 0 },
map: {
version: 3,
sources: ["before.ts"],
sourcesContent: ["export const before = 1;"],
names: [],
mappings: "AAAA",
},
},
{ offset: { line: 1, column: 0 }, url },
{
offset: { line: 2, column: 0 },
map: {
version: 3,
sources: ["after.ts"],
sourcesContent: ["export const after = 2;"],
names: [],
mappings: "AAAA",
},
},
],
});
await writeFixtureFiles(root, { "indexed.js.map": mapText });
const { graph } = await analyzeJavaScriptDirectory(surface, cli, root);
const mapDigest = createHash("sha256").update(mapText).digest("hex");
expect(graph.coverage.status).toBe("partial");
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "unknown",
observations: expect.arrayContaining([
expect.objectContaining({
properties: expect.objectContaining({
path: "indexed.js.map",
operation: "parse-local-source-map",
limitation: `External source-map section URL is not fetched: ${url}`,
external_sections: [{ section_path: [1], url }],
}),
evidence: expect.objectContaining({
artifact: expect.objectContaining({ sha256: mapDigest }),
limitations: [
`External source-map section URL is not fetched: ${url}`,
],
}),
}),
]),
}),
);
for (const [source, content] of [
["before.ts", "export const before = 1;"],
["after.ts", "export const after = 2;"],
] as const)
expect(graph.nodes).toContainEqual(
expect.objectContaining({
kind: "source-module",
identity: expect.objectContaining({
strategy: "source-map-original",
source_map_sha256: mapDigest,
original_source: source,
source_sha256: createHash("sha256").update(content).digest("hex"),
}),
}),
);
expect(await readFile(join(root, "indexed.js.map"), "utf8")).toBe(mapText);
},
);