diff --git a/packages/babel-core/package.json b/packages/babel-core/package.json index ea945a5ab950..c47dd20ceef8 100644 --- a/packages/babel-core/package.json +++ b/packages/babel-core/package.json @@ -48,7 +48,7 @@ "./src/transformation/util/clone-deep.ts": "./src/transformation/util/clone-deep-browser.ts" }, "dependencies": { - "@ampproject/remapping": "^2.0.0", + "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "workspace:^", "@babel/generator": "workspace:^", "@babel/helper-compilation-targets": "workspace:^", diff --git a/packages/babel-core/src/transformation/file/merge-map.ts b/packages/babel-core/src/transformation/file/merge-map.ts index 614c743f9e58..1742db173f67 100644 --- a/packages/babel-core/src/transformation/file/merge-map.ts +++ b/packages/babel-core/src/transformation/file/merge-map.ts @@ -6,24 +6,20 @@ export default function mergeSourceMap( map: SourceMap, source: string, ): SourceMap { - const outputSources = map.sources; - - let result; - if (outputSources.length > 1) { - // When there are multiple output sources, we can't always be certain which - // source represents the file we just transformed. - const index = outputSources.indexOf(source); - - // If we can't find the source, we fall back to the legacy behavior of - // outputting an empty sourcemap. - if (index === -1) { - result = emptyMap(inputMap); - } else { - result = mergeMultiSource(inputMap, map, index); + const result = remapping(rootless(map), (s, ctx) => { + if (s === source) { + // We empty the source location, which will prevent the sourcemap from + // becoming relative to the input's location. Eg, if we're transforming a + // file 'foo/bar.js', and it is a transformation of a `baz.js` file in the + // same directory, the expected output is just `baz.js`. Without this step, + // it would become `foo/baz.js`. + ctx.source = ""; + + return rootless(inputMap); } - } else { - result = mergeSingleSource(inputMap, map); - } + + return null; + }); if (typeof inputMap.sourceRoot === "string") { result.sourceRoot = inputMap.sourceRoot; @@ -31,52 +27,6 @@ export default function mergeSourceMap( return result; } -// A single source transformation is the default, and easiest to handle. -function mergeSingleSource(inputMap: SourceMap, map: SourceMap): SourceMap { - return remapping([rootless(map), rootless(inputMap)], () => null); -} - -// Transformation generated an output from multiple source files. When this -// happens, it's ambiguous which source was the transformed file, and which -// source is from the transformation process. We use remapping's multisource -// behavior, returning the input map when we encounter the transformed file. -function mergeMultiSource(inputMap: SourceMap, map: SourceMap, index: number) { - // We empty the source index, which will prevent the sourcemap from becoming - // relative the the input's location. Eg, if we're transforming a file - // 'foo/bar.js', and it is a transformation of a `baz.js` file in the same - // directory, the expected output is just `baz.js`. Without this step, it - // would become `foo/baz.js`. - map.sources[index] = ""; - - let count = 0; - return remapping(rootless(map), () => { - if (count++ === index) return rootless(inputMap); - return null; - }); -} - -// Legacy behavior of the old merger was to output a sourcemap without any -// mappings but with copied sourcesContent. This only happens if there are -// multiple output files and it's ambiguous which one is the transformed file. -function emptyMap(inputMap: SourceMap) { - const inputSources = inputMap.sources; - - const sources = []; - const sourcesContent = inputMap.sourcesContent?.filter((content, i) => { - if (typeof content !== "string") return false; - - sources.push(inputSources[i]); - return true; - }); - - return { - ...inputMap, - sources, - sourcesContent, - mappings: "", - }; -} - function rootless(map: SourceMap): SourceMap { return { ...map, diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js new file mode 100644 index 000000000000..298b7dfa0cb8 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js @@ -0,0 +1,5 @@ +foo(1); +function foo(bar) { + throw new Error('Intentional.'); +} +//# sourceMappingURL=input.js.map diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js.map b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js.map new file mode 100644 index 000000000000..4f1791f12c23 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/input.js.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "sources": ["input.tsx"], + "names": [], + "mappings": "AAAA,GAAG,CAAC,CAAC,CAAC,CAAC;AACP,SAAS,GAAG,CAAC,GAAW;IACpB,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;AACpC,CAAC", + "sourcesContent": [ + "foo(1);\nfunction foo(bar: number): never {\n throw new Error('Intentional.');\n}" + ] +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/options.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/options.json new file mode 100644 index 000000000000..6466aed13cd6 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/options.json @@ -0,0 +1,4 @@ +{ + "inputSourceMap": true, + "plugins": ["./plugin.js"] +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/output.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/output.js new file mode 100644 index 000000000000..b0630eb937f4 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/output.js @@ -0,0 +1,2 @@ +"bar"; +"baz"; diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/plugin.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/plugin.js new file mode 100644 index 000000000000..1d9b56675062 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/plugin.js @@ -0,0 +1,52 @@ +module.exports = function (babel) { + const { types: t } = babel; + + return { + visitor: { + Program(path) { + const { file } = this; + const { sourceFileName } = file.opts.generatorOpts; + + // This injects the sourcesContent, though I don't imagine anyone's + // doing it. + file.code = { + [sourceFileName]: file.code, + 'bar.js': '', + 'baz.js': 'baz();', + }; + }, + + CallExpression(path) { + const callee = path.node; + const { loc } = callee; + + // This filename will cause a second source file to be generated in the + // output sourcemap. + loc.filename = "bar.js"; + loc.start.column = 1; + loc.end.column = 4; + + const node = t.stringLiteral('bar'); + node.loc = loc; + path.replaceWith(node); + }, + + Function(path) { + const callee = path.node; + const { loc } = callee; + + // This filename will cause a second source file to be generated in the + // output sourcemap. + loc.filename = "baz.js"; + loc.start.column = 0; + loc.start.line = 1; + loc.end.column = 3; + loc.end.line = 1; + + const node = t.stringLiteral('baz'); + node.loc = loc; + path.replaceWith(node); + }, + }, + }; +}; diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/source-map.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/source-map.json new file mode 100644 index 000000000000..3063977828b6 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources-complete-replace/source-map.json @@ -0,0 +1,13 @@ +{ + "mappings": "AAAC;ACAD,K", + "names": [], + "sources": [ + "bar.js", + "baz.js" + ], + "sourcesContent": [ + "", + "baz();" + ], + "version": 3 +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js index b9fd011c05bf..a5470426bf56 100644 --- a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-multiple-output-sources/plugin.js @@ -20,7 +20,7 @@ module.exports = function (babel) { path.replaceWith(node); // This injects the sourcesContent, though I don't imagine anyone's - // doing it. + // doing it. file.code = { [sourceFileName]: file.code, 'test.js': '', diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js new file mode 100644 index 000000000000..298b7dfa0cb8 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js @@ -0,0 +1,5 @@ +foo(1); +function foo(bar) { + throw new Error('Intentional.'); +} +//# sourceMappingURL=input.js.map diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js.map b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js.map new file mode 100644 index 000000000000..4f1791f12c23 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/input.js.map @@ -0,0 +1,9 @@ +{ + "version": 3, + "sources": ["input.tsx"], + "names": [], + "mappings": "AAAA,GAAG,CAAC,CAAC,CAAC,CAAC;AACP,SAAS,GAAG,CAAC,GAAW;IACpB,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;AACpC,CAAC", + "sourcesContent": [ + "foo(1);\nfunction foo(bar: number): never {\n throw new Error('Intentional.');\n}" + ] +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/options.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/options.json new file mode 100644 index 000000000000..6466aed13cd6 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/options.json @@ -0,0 +1,4 @@ +{ + "inputSourceMap": true, + "plugins": ["./plugin.js"] +} diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/output.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/output.js new file mode 100644 index 000000000000..e4b5496b38df --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/output.js @@ -0,0 +1 @@ +"bar"; diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/plugin.js b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/plugin.js new file mode 100644 index 000000000000..a6d943636079 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/plugin.js @@ -0,0 +1,38 @@ +module.exports = function (babel) { + const { types: t } = babel; + + return { + visitor: { + Program(path) { + const { file } = this; + const { sourceFileName } = file.opts.generatorOpts; + + // This injects the sourcesContent, though I don't imagine anyone's + // doing it. + file.code = { + [sourceFileName]: file.code, + 'test.js': '', + }; + }, + + CallExpression(path) { + const callee = path.node; + const { loc } = callee; + + // This filename will cause a second source file to be generated in the + // output sourcemap. + loc.filename = "test.js"; + loc.start.column = 1; + loc.end.column = 4; + + const node = t.stringLiteral('bar'); + node.loc = loc; + path.replaceWith(node); + }, + + Function(path) { + path.remove(); + }, + }, + }; +}; diff --git a/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/source-map.json b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/source-map.json new file mode 100644 index 000000000000..3259064134d2 --- /dev/null +++ b/packages/babel-core/test/fixtures/transformation/source-maps/input-source-map-sources-complete-replace/source-map.json @@ -0,0 +1,7 @@ +{ + "mappings": "AAAC", + "names": [], + "sources": ["test.js"], + "sourcesContent": [""], + "version": 3 +} diff --git a/yarn.lock b/yarn.lock index bd558cd6df86..462cd873a908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,13 +5,12 @@ __metadata: version: 5 cacheKey: 8 -"@ampproject/remapping@npm:^2.0.0": - version: 2.0.2 - resolution: "@ampproject/remapping@npm:2.0.2" +"@ampproject/remapping@npm:^2.0.0, @ampproject/remapping@npm:^2.1.0": + version: 2.1.0 + resolution: "@ampproject/remapping@npm:2.1.0" dependencies: - "@jridgewell/trace-mapping": ^0.2.2 - sourcemap-codec: 1.4.8 - checksum: 5759df3715f0291cbf97099a9bb7202201a1a267e232ee1505418c768b9ae7281cd550b1da563a12808a06529eb1298744a6cabde21ac354fc8450044c7f2213 + "@jridgewell/trace-mapping": ^0.3.0 + checksum: 10ff0d4a559f930082f1a4c1b68dc521d5b1a75e0b8ab4829e9eedf6621386893e4a008f0db6c716f64db5d8eed49c0abcfbf3bd6ff11d5a00312454a9351ed4 languageName: node linkType: hard @@ -323,7 +322,7 @@ __metadata: version: 0.0.0-use.local resolution: "@babel/core@workspace:packages/babel-core" dependencies: - "@ampproject/remapping": ^2.0.0 + "@ampproject/remapping": ^2.1.0 "@babel/code-frame": "workspace:^" "@babel/generator": "workspace:^" "@babel/helper-compilation-targets": "workspace:^" @@ -4091,13 +4090,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.2.2": - version: 0.2.5 - resolution: "@jridgewell/trace-mapping@npm:0.2.5" +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.10 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.10" + checksum: 247229218edbe165dcf0a5ae0c4b81bff1b5438818bb09221f756681fe158597fdf25c2a803f9260453b299c98c7e01ddebeb1555cda3157d987cd22c08605ef + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.0": + version: 0.3.2 + resolution: "@jridgewell/trace-mapping@npm:0.3.2" dependencies: "@jridgewell/resolve-uri": ^3.0.3 - sourcemap-codec: 1.4.8 - checksum: 7ac0a2992f4a8d16c1cbe03bccbcfbd1e96bf5071b0a794dd97904a7588cc248b73e8091fabcb13dac8f40bc51297ee4ef98a6a870ca5a4dfb8e2dcbf6f33956 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: b58be6b4133cbcb20bfd28c9ca843b8db9efa0bf1d7e0e9e26b2228dace94ad53161c996ab1d762d7c3955dfc398a7734e7b84a2493ae36b451f232234fbb257 languageName: node linkType: hard @@ -14168,7 +14174,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"sourcemap-codec@npm:1.4.8, sourcemap-codec@npm:^1.4.4": +"sourcemap-codec@npm:^1.4.4": version: 1.4.8 resolution: "sourcemap-codec@npm:1.4.8" checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316