fix(web): retain source-map declaration provenance

This commit is contained in:
morluto
2026-10-27 07:35:04 +08:00
parent 59a70adefd
commit e80fb638be
4 changed files with 124 additions and 2 deletions
+5
View File
@@ -121,6 +121,11 @@ preceding browser mappings when a nested section starts with a gap or has no
child leaves. Raw map text, original source identities and the dedicated decoder
profile are preserved.
Browser `original_module_edges` carry a `declaration_index` joining each edge to
its `original_sources` declaration, including repeated source URLs across indexed
sections. Imports are deduplicated within each declaration. Older observations
may omit the index; their edge declaration identity remains unknown.
Browser collection accepts up to 64 MiB of map responses per inspection and
preflights indexed sections, sources, names, generated rows and mapping segments
against a 262144-record decode budget before upstream flattening. Indexed maps
+3 -2
View File
@@ -868,7 +868,6 @@ const originalModuleEdges = (
) => void,
): OriginalModuleEdges => {
const edges: ParsedSourceMapItem["original_module_edges"] = [];
const seen = new Set<string>();
const incomplete: string[] = [];
for (const source of sources) {
check();
@@ -892,6 +891,7 @@ const originalModuleEdges = (
// A recovered program still yields the imports it did parse, but nodes
// after an unrecoverable point are missing, so the edges are a subset.
if (parsed.errors.length > 0) incomplete.push(sourceName);
const seen = new Set<string>();
let unboundRequires: ReadonlySet<string> | undefined;
traverseJavaScriptAst(parsed, {
enter: (node) => {
@@ -920,10 +920,11 @@ const originalModuleEdges = (
)
return;
}
const key = `${sourceName}\0${kind}\0${specifier}`;
const key = `${kind}\0${specifier}`;
if (seen.has(key)) return;
seen.add(key);
const edge = {
declaration_index: source.declaration_index,
from_source: sourceName,
kind,
specifier,
+2
View File
@@ -254,6 +254,8 @@ const originalSourceSchema = z.object({
artifact: webTextArtifactSchema.nullable(),
});
const originalModuleEdgeSchema = z.object({
// Historical observations omit this link to original_sources declarations.
declaration_index: z.int().min(0).optional(),
from_source: z.string(),
kind: z.enum(["static_import", "dynamic_import", "require"]),
specifier: z.string(),
@@ -31,6 +31,120 @@ const expectInvalidIncludedSourceMaps = (result: WebSourceMaps): void => {
expectInvalidSourceMaps({ ...result, status: "unavailable" });
};
it.each(["regular", "indexed"])(
"retains declaration identity for duplicate source URLs in HTTP %s maps",
async (format) => {
const contents = [
null,
'import "./shared.js"; import "./shared.js"; import "./first.js";',
'import "./shared.js"; import "./second.js";',
];
const leaf = (sourcesContent: (string | null)[]) => ({
version: 3,
sources: sourcesContent.map(() => "same.ts"),
sourcesContent,
names: [],
mappings: "",
});
const map =
format === "regular"
? leaf(contents)
: {
version: 3,
sections: contents.map((content, line) => ({
offset: { line, column: 0 },
map: leaf([content]),
})),
};
const server = createServer((_incoming, response) => {
response.setHeader("content-type", "application/source-map+json");
response.end(JSON.stringify(map));
});
try {
await new Promise<void>((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
if (address === null || typeof address === "string")
throw new TypeError("Expected a TCP listener address");
const localOrigin = `http://127.0.0.1:${String(address.port)}`;
const url = `${localOrigin}/app.js.map`;
const result = await fetchWebSourceMapsOk(
[{ ...request, fetchUrl: url, declaredUrl: url }],
input({ allowed_origins: [localOrigin] }),
);
const item = result.items[0];
if (item?.status !== "included") throw new Error("map not included");
expect(item.limitation).toBeNull();
expect(
item.original_sources.map((source) => ({
declaration_index: source.declaration_index,
source_index: source.source_index,
source: source.source,
text: source.artifact?.text ?? null,
})),
).toEqual(
contents.map((text, declaration_index) => ({
declaration_index,
source_index: format === "regular" ? declaration_index : 0,
source: `${localOrigin}/same.ts`,
text,
})),
);
expect(item.original_module_edges).toEqual(
[
[1, "./shared.js"],
[1, "./first.js"],
[2, "./shared.js"],
[2, "./second.js"],
].map(([declaration_index, specifier]) => ({
declaration_index,
from_source: `${localOrigin}/same.ts`,
kind: "static_import",
specifier,
resolved_source: new URL(String(specifier), url).href,
})),
);
expect(webSourceMapsSchema.parse(result)).toEqual(result);
const historical = {
...result,
items: [
{
...item,
original_module_edges: item.original_module_edges.map(
({ declaration_index: _index, ...edge }) => edge,
),
},
],
};
expect(webSourceMapsSchema.parse(historical)).toEqual(historical);
expectInvalidSourceMaps({
...result,
items: [
{
...item,
original_module_edges: [
{
...item.original_module_edges[0],
declaration_index: -1,
},
],
},
],
});
} finally {
server.closeAllConnections();
if (server.listening)
await new Promise<void>((resolve, reject) => {
server.close((error) =>
error === undefined ? resolve() : reject(error),
);
});
}
},
);
it.each(["regular", "indexed"])(
"collects an HTTP %s source map with omitted names without accepting invalid names",
async (format) => {