Add sourcemap markings for each line of a string (#12086)

* Add sourcemap markings for each line of a string

Fixes https://github.com/babel/babel/issues/12083

* Fix for multiple newlines

* Optimize with indexOf

* Comment explaining newline search
This commit is contained in:
Justin Ridgewell 2020-09-22 21:36:21 -04:00 committed by GitHub
parent a4a14caee7
commit f49234aa69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 165 additions and 20 deletions

View File

@ -116,9 +116,46 @@ export default class Buffer {
filename: ?string, filename: ?string,
force?: boolean, force?: boolean,
): void { ): void {
// If there the line is ending, adding a new mapping marker is redundant this._buf.push(str);
if (this._map && str[0] !== "\n") { this._last = str[str.length - 1];
this._map.mark(
// 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.line,
this._position.column, this._position.column,
line, line,
@ -129,19 +166,6 @@ export default class Buffer {
); );
} }
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++;
}
}
}
removeTrailingNewline(): void { removeTrailingNewline(): void {
if (this._queue.length > 0 && this._queue[0][0] === "\n") { if (this._queue.length > 0 && this._queue[0][0] === "\n") {
this._queue.shift(); this._queue.shift();

View File

@ -0,0 +1,6 @@
"before\
after";
"before\
\
after";

View File

@ -0,0 +1,5 @@
"before\
after";
"before\
\
after";

View File

@ -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
}

View File

@ -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`;

View File

@ -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`;

View File

@ -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
}

View File

@ -5,6 +5,7 @@ import * as t from "@babel/types";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import fixtures from "@babel/helper-fixtures"; import fixtures from "@babel/helper-fixtures";
import sourcemap from "source-map";
describe("generation", function () { describe("generation", function () {
it("completeness", function () { it("completeness", function () {
@ -277,6 +278,48 @@ describe("generation", function () {
expect(generated.code).toBe("function foo2() {\n bar2;\n}"); 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 () { it("lazy source map generation", function () {
const code = "function hi (msg) { console.log(msg); }\n"; const code = "function hi (msg) { console.log(msg); }\n";