diff --git a/packages/babel-core/src/transformation/file/merge-map.ts b/packages/babel-core/src/transformation/file/merge-map.ts
index 614c743f9e58..dc7227e23031 100644
--- a/packages/babel-core/src/transformation/file/merge-map.ts
+++ b/packages/babel-core/src/transformation/file/merge-map.ts
@@ -8,73 +8,32 @@ export default function mergeSourceMap(
): 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);
- }
- } else {
- result = mergeSingleSource(inputMap, map);
+ const index = outputSources.indexOf(source);
+
+ if (index > -1) {
+ // We empty the source index, 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`.
+ map.sources[index] = "";
}
- if (typeof inputMap.sourceRoot === "string") {
- result.sourceRoot = inputMap.sourceRoot;
- }
- 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), () => {
+ const result = remapping(rootless(map), () => {
+ // Return the inputMap only when we hit the correct source index, so that
+ // remapping will trace through it into the original locations. Remapping
+ // calls the loader cb for each source in depth-first order, and since
+ // we're not returning any other sourcemaps, the source is guaranteed to be
+ // the index'th call.
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: "",
- };
+ if (typeof inputMap.sourceRoot === "string") {
+ result.sourceRoot = inputMap.sourceRoot;
+ }
+ return result;
}
function rootless(map: SourceMap): SourceMap {
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
+}