diff --git a/packages/babel-generator/src/buffer.js b/packages/babel-generator/src/buffer.js index 2c9c6d784927..2714bf3fcf04 100644 --- a/packages/babel-generator/src/buffer.js +++ b/packages/babel-generator/src/buffer.js @@ -116,30 +116,54 @@ export default class Buffer { filename: ?string, force?: boolean, ): void { - // If there the line is ending, adding a new mapping marker is redundant - if (this._map && str[0] !== "\n") { - this._map.mark( - this._position.line, - this._position.column, - line, - column, - identifierName, - filename, - force, - ); - } - this._buf.push(str); this._last = str[str.length - 1]; - for (let i = 0; i < str.length; i++) { - if (str[i] === "\n") { - this._position.line++; - this._position.column = 0; - } else { - this._position.column++; + // Search for newline chars. We search only for `\n`, since both `\r` and + // `\r\n` are normalized to `\n` during parse. We exclude `\u2028` and + // `\u2029` for performance reasons, they're so uncommon that it's probably + // ok. It's also unclear how other sourcemap utilities handle them... + let i = str.indexOf("\n"); + let last = 0; + + // If the string starts with a newline char, then adding a mark is redundant. + // This catches both "no newlines" and "newline after several chars". + if (i !== 0) { + this._mark(line, column, identifierName, filename, force); + } + + // Now, find each reamining newline char in the string. + while (i !== -1) { + this._position.line++; + this._position.column = 0; + last = i + 1; + + // We mark the start of each line, which happens directly after this newline char + // unless this is the last char. + if (last < str.length) { + this._mark(++line, 0, identifierName, filename, force); } + i = str.indexOf("\n", last); } + this._position.column += str.length - last; + } + + _mark( + line: number, + column: number, + identifierName: ?string, + filename: ?string, + force?: boolean, + ): void { + this._map?.mark( + this._position.line, + this._position.column, + line, + column, + identifierName, + filename, + force, + ); } removeTrailingNewline(): void { diff --git a/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/input.js b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/input.js new file mode 100644 index 000000000000..fd9b928d953e --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/input.js @@ -0,0 +1,6 @@ +"before\ +after"; + +"before\ +\ +after"; diff --git a/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/output.js b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/output.js new file mode 100644 index 000000000000..bb7dc50c429f --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/output.js @@ -0,0 +1,5 @@ +"before\ +after"; +"before\ +\ +after"; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/source-map.json b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/source-map.json new file mode 100644 index 000000000000..b2fc354d8f32 --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/string-literal-newline/source-map.json @@ -0,0 +1,9 @@ +{ + "mappings": "AAAA;AACA,MADA;AAGA;AACA;AACA,MAFA", + "names": [], + "sources": ["fixtures/sourcemaps/string-literal-newline/input.js"], + "sourcesContent": [ + "\"before\\\nafter\";\n\n\"before\\\n\\\nafter\";" + ], + "version": 3 +} diff --git a/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/input.js b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/input.js new file mode 100644 index 000000000000..8232c725abe4 --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/input.js @@ -0,0 +1,27 @@ +// Newline +`before +after`; + +// Newline newline +`before + +after`; + +// Newline LineContinuation +`before +\ +after`; + +// LineContinuation +`before\ +after`; + +// LineContinuation newline +`before\ + +after`; + +// LineContinuation LineContinuation +`before\ +\ +after`; diff --git a/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/output.js b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/output.js new file mode 100644 index 000000000000..92419ace8be6 --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/output.js @@ -0,0 +1,22 @@ +// Newline +`before +after`; // Newline newline + +`before + +after`; // Newline LineContinuation + +`before +\ +after`; // LineContinuation + +`before\ +after`; // LineContinuation newline + +`before\ + +after`; // LineContinuation LineContinuation + +`before\ +\ +after`; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/source-map.json b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/source-map.json new file mode 100644 index 000000000000..083336baf672 --- /dev/null +++ b/packages/babel-generator/test/fixtures/sourcemaps/template-literal-newline/source-map.json @@ -0,0 +1,9 @@ +{ + "mappings": "AAAA;AACC;AACD,MADA,C,CAGA;;AACC;AACD;AACA,MAFA,C,CAIA;;AACC;AACD;AACA,MAFA,C,CAIA;;AACC;AACD,MADA,C,CAGA;;AACC;AACD;AACA,MAFA,C,CAIA;;AACC;AACD;AACA,MAFA", + "names": [], + "sources": ["fixtures/sourcemaps/template-literal-newline/input.js"], + "sourcesContent": [ + "// Newline\n`before\nafter`;\n\n// Newline newline\n`before\n\nafter`;\n\n// Newline LineContinuation\n`before\n\\\nafter`;\n\n// LineContinuation\n`before\\\nafter`;\n\n// LineContinuation newline\n`before\\\n\nafter`;\n\n// LineContinuation LineContinuation\n`before\\\n\\\nafter`;" + ], + "version": 3 +} diff --git a/packages/babel-generator/test/index.js b/packages/babel-generator/test/index.js index 25a93045ddcc..8c6075431709 100644 --- a/packages/babel-generator/test/index.js +++ b/packages/babel-generator/test/index.js @@ -5,6 +5,7 @@ import * as t from "@babel/types"; import fs from "fs"; import path from "path"; import fixtures from "@babel/helper-fixtures"; +import sourcemap from "source-map"; describe("generation", function () { it("completeness", function () { @@ -277,6 +278,48 @@ describe("generation", function () { expect(generated.code).toBe("function foo2() {\n bar2;\n}"); }); + it("newline in template literal", () => { + const code = "`before\n\nafter`;"; + const ast = parse(code, { filename: "inline" }).program; + const generated = generate( + ast, + { + filename: "inline", + sourceFileName: "inline", + sourceMaps: true, + }, + code, + ); + + const consumer = new sourcemap.SourceMapConsumer(generated.map); + const loc = consumer.originalPositionFor({ line: 2, column: 1 }); + expect(loc).toMatchObject({ + column: 0, + line: 2, + }); + }); + + it("newline in string literal", () => { + const code = "'before\\\n\\\nafter';"; + const ast = parse(code, { filename: "inline" }).program; + const generated = generate( + ast, + { + filename: "inline", + sourceFileName: "inline", + sourceMaps: true, + }, + code, + ); + + const consumer = new sourcemap.SourceMapConsumer(generated.map); + const loc = consumer.originalPositionFor({ line: 2, column: 1 }); + expect(loc).toMatchObject({ + column: 0, + line: 2, + }); + }); + it("lazy source map generation", function () { const code = "function hi (msg) { console.log(msg); }\n";