From 1016c96e6ba920c1c7da6898db16929145c749be Mon Sep 17 00:00:00 2001 From: Yann Roberto <165009858+shoyann@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:17:09 +0800 Subject: [PATCH] fix(source-maps): accept omitted names during browser collection (#2831) --- src/javascript/sourceMaps/SourceMapFormat.ts | 5 - .../browser/webSourceMapFetcher.test.ts | 98 +++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/javascript/sourceMaps/SourceMapFormat.ts b/src/javascript/sourceMaps/SourceMapFormat.ts index fc3d3e8cd..f8975c9b6 100644 --- a/src/javascript/sourceMaps/SourceMapFormat.ts +++ b/src/javascript/sourceMaps/SourceMapFormat.ts @@ -267,11 +267,6 @@ const inspectLeaf = ( consumeRecords(budget, Array.isArray(rawNames) ? rawNames.length : 0); consumeRecords(budget, Array.isArray(rawContents) ? rawContents.length : 0); options.check?.(); - if (options.profile === "browser-collection" && !Array.isArray(rawNames)) - throw new SourceMapFormatFailure( - "format", - "Source-map leaf requires a names array.", - ); if ( options.profile === "browser-collection" && !hasValidSourceMapContents( diff --git a/tests/boundary/browser/webSourceMapFetcher.test.ts b/tests/boundary/browser/webSourceMapFetcher.test.ts index b1227fa26..6dcabb75b 100644 --- a/tests/boundary/browser/webSourceMapFetcher.test.ts +++ b/tests/boundary/browser/webSourceMapFetcher.test.ts @@ -29,6 +29,104 @@ const expectInvalidIncludedSourceMaps = (result: WebSourceMaps): void => { expectInvalidSourceMaps({ ...result, status: "unavailable" }); }; +it.each(["regular", "indexed"])( + "collects an HTTP %s source map with omitted names without accepting invalid names", + async (format) => { + const leaf = { + version: 3, + sources: ["src/main.ts"], + sourcesContent: ["import './dependency.ts';"], + mappings: "AAAA", + }; + const texts = [ + leaf, + { ...leaf, names: [] }, + { ...leaf, names: null }, + { ...leaf, names: 42 }, + { ...leaf, names: [42] }, + { ...leaf, mappings: "AAAAA" }, + ].map((map) => + JSON.stringify( + format === "indexed" + ? { + version: 3, + sections: [{ offset: { line: 2, column: 5 }, map }], + } + : map, + ), + ); + const server = createServer((incoming, response) => { + response.setHeader("content-type", "application/source-map+json"); + response.end(texts[Number(incoming.url?.slice(1))]); + }); + try { + await new Promise((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 result = await fetchWebSourceMaps( + texts.map((_, index) => ({ + ...request, + fetchUrl: `${localOrigin}/${index}`, + declaredUrl: `${localOrigin}/${index}`, + })), + input({ allowed_origins: [localOrigin] }), + ); + expect(result.items.map(({ status }) => status)).toEqual([ + "included", + "included", + "invalid", + "invalid", + "invalid", + "invalid", + ]); + for (const [index, text] of texts.slice(0, 2).entries()) + expect(result.items[index]).toMatchObject({ + artifact: { + text, + bytes: Buffer.byteLength(text), + sha256: createHash("sha256").update(text).digest("hex"), + }, + original_sources: [{ source: `${localOrigin}/src/main.ts` }], + mappings: [ + { + generated_line: format === "indexed" ? 3 : 1, + generated_column: format === "indexed" ? 5 : 0, + source: `${localOrigin}/src/main.ts`, + original_line: 1, + original_column: 0, + name: null, + }, + ], + original_module_edges: [ + { + from_source: `${localOrigin}/src/main.ts`, + resolved_source: `${localOrigin}/src/dependency.ts`, + }, + ], + }); + for (const item of result.items.slice(2)) + expect(item).toMatchObject({ + artifact: null, + mappings: [], + original_sources: [], + }); + } finally { + server.closeAllConnections(); + if (server.listening) + await new Promise((resolve, reject) => { + server.close((error) => + error === undefined ? resolve() : reject(error), + ); + }); + } + }, +); + it("parses ECMA-426 HTTP source-map prefixes while retaining exact response evidence", async () => { const map = JSON.stringify({ version: 3,