Skip to content

Commit 4fb29a3

Browse files
Never throw for invalid escapes in tagged templates (#14964)
1 parent 98c3bb9 commit 4fb29a3

10 files changed

Lines changed: 155 additions & 52 deletions

File tree

packages/babel-helper-string-parser/src/index.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function readStringContents(
5959
const initialCurLine = curLine;
6060

6161
let out = "";
62-
let containsInvalid = false;
62+
let firstInvalidLoc = null;
6363
let chunkStart = pos;
6464
const { length } = input;
6565
for (;;) {
@@ -75,25 +75,20 @@ export function readStringContents(
7575
}
7676
if (ch === charCodes.backslash) {
7777
out += input.slice(chunkStart, pos);
78-
let escaped;
79-
({
80-
ch: escaped,
81-
pos,
82-
lineStart,
83-
curLine,
84-
} = readEscapedChar(
78+
const res = readEscapedChar(
8579
input,
8680
pos,
8781
lineStart,
8882
curLine,
8983
type === "template",
9084
errors,
91-
));
92-
if (escaped === null) {
93-
containsInvalid = true;
85+
);
86+
if (res.ch === null && !firstInvalidLoc) {
87+
firstInvalidLoc = { pos, lineStart, curLine };
9488
} else {
95-
out += escaped;
89+
out += res.ch;
9690
}
91+
({ pos, lineStart, curLine } = res);
9792
chunkStart = pos;
9893
} else if (
9994
ch === charCodes.lineSeparator ||
@@ -121,7 +116,17 @@ export function readStringContents(
121116
++pos;
122117
}
123118
}
124-
return { pos, str: out, containsInvalid, lineStart, curLine };
119+
return {
120+
pos,
121+
str: out,
122+
firstInvalidLoc,
123+
lineStart,
124+
curLine,
125+
126+
// TODO(Babel 8): This is only needed for backwards compatibility,
127+
// we can remove it.
128+
containsInvalid: !!firstInvalidLoc,
129+
};
125130
}
126131

127132
function isStringEnd(
@@ -280,6 +285,7 @@ function readHexChar(
280285
forceLen,
281286
false,
282287
errors,
288+
/* bailOnError */ !throwOnInvalid,
283289
));
284290
if (n === null) {
285291
if (throwOnInvalid) {
@@ -322,6 +328,7 @@ export function readInt(
322328
forceLen: boolean,
323329
allowNumSeparator: boolean | "bail",
324330
errors: IntErrorHandlers,
331+
bailOnError: boolean,
325332
) {
326333
const start = pos;
327334
const forbiddenSiblings =
@@ -349,13 +356,15 @@ export function readInt(
349356
const next = input.charCodeAt(pos + 1);
350357

351358
if (!allowNumSeparator) {
359+
if (bailOnError) return { n: null, pos };
352360
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
353361
} else if (
354362
Number.isNaN(next) ||
355363
!isAllowedSibling(next) ||
356364
forbiddenSiblings.has(prev) ||
357365
forbiddenSiblings.has(next)
358366
) {
367+
if (bailOnError) return { n: null, pos };
359368
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
360369
}
361370

@@ -376,7 +385,12 @@ export function readInt(
376385
if (val >= radix) {
377386
// If we found a digit which is too big, errors.invalidDigit can return true to avoid
378387
// breaking the loop (this is used for error recovery).
379-
if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
388+
if (val <= 9 && bailOnError) {
389+
return { n: null, pos };
390+
} else if (
391+
val <= 9 &&
392+
errors.invalidDigit(pos, lineStart, curLine, radix)
393+
) {
380394
val = 0;
381395
} else if (forceLen) {
382396
val = 0;

packages/babel-parser/src/parser/expression.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,8 +2010,11 @@ export default abstract class ExpressionParser extends LValParser {
20102010
if (value === null) {
20112011
if (!isTagged) {
20122012
this.raise(Errors.InvalidEscapeSequenceTemplate, {
2013-
// FIXME: explain
2014-
at: createPositionWithColumnOffset(startLoc, 2),
2013+
// FIXME: Adding 1 is probably wrong.
2014+
at: createPositionWithColumnOffset(
2015+
this.state.firstInvalidTemplateEscapePos,
2016+
1,
2017+
),
20152018
});
20162019
}
20172020
}

packages/babel-parser/src/tokenizer/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,7 @@ export default abstract class Tokenizer extends CommentsParser {
11311131
forceLen,
11321132
allowNumSeparator,
11331133
this.errorHandlers_readInt,
1134+
/* bailOnError */ false,
11341135
);
11351136
this.state.pos = pos;
11361137
return n;
@@ -1318,7 +1319,7 @@ export default abstract class Tokenizer extends CommentsParser {
13181319
// Reads template string tokens.
13191320
readTemplateToken(): void {
13201321
const opening = this.input[this.state.pos];
1321-
const { str, containsInvalid, pos, curLine, lineStart } =
1322+
const { str, firstInvalidLoc, pos, curLine, lineStart } =
13221323
readStringContents(
13231324
"template",
13241325
this.input,
@@ -1331,16 +1332,24 @@ export default abstract class Tokenizer extends CommentsParser {
13311332
this.state.lineStart = lineStart;
13321333
this.state.curLine = curLine;
13331334

1335+
if (firstInvalidLoc) {
1336+
this.state.firstInvalidTemplateEscapePos = new Position(
1337+
firstInvalidLoc.curLine,
1338+
firstInvalidLoc.pos - firstInvalidLoc.lineStart,
1339+
firstInvalidLoc.pos,
1340+
);
1341+
}
1342+
13341343
if (this.input.codePointAt(pos) === charCodes.graveAccent) {
13351344
this.finishToken(
13361345
tt.templateTail,
1337-
containsInvalid ? null : opening + str + "`",
1346+
firstInvalidLoc ? null : opening + str + "`",
13381347
);
13391348
} else {
13401349
this.state.pos++; // skip '{'
13411350
this.finishToken(
13421351
tt.templateNonTail,
1343-
containsInvalid ? null : opening + str + "${",
1352+
firstInvalidLoc ? null : opening + str + "${",
13441353
);
13451354
}
13461355
}

packages/babel-parser/src/tokenizer/state.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ export default class State {
135135
// escape sequences must not be interpreted as keywords.
136136
containsEsc: boolean = false;
137137

138+
// Used to track invalid escape sequences in template literals,
139+
// that must be reported if the template is not tagged.
140+
firstInvalidTemplateEscapePos: null | Position = null;
141+
138142
// This property is used to track the following errors
139143
// - StrictNumericEscape
140144
// - StrictOctalLiteral

packages/babel-parser/test/fixtures/es2015/template/error-after-newline/output.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"type": "File",
33
"start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":2,"index":14}},
44
"errors": [
5-
"SyntaxError: Numeric separators are not allowed inside unicode escape sequences or hex escape sequences. (2:5)"
5+
"SyntaxError: Invalid escape sequence in template. (2:1)"
66
],
77
"program": {
88
"type": "Program",
@@ -23,7 +23,7 @@
2323
"start":1,"end":12,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":3,"column":0,"index":12}},
2424
"value": {
2525
"raw": "\n\\u{12_34}\n",
26-
"cooked": "\n\n"
26+
"cooked": null
2727
},
2828
"tail": true
2929
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tag`abc\u{1000_0000}`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"type": "File",
3+
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
4+
"program": {
5+
"type": "Program",
6+
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
7+
"sourceType": "script",
8+
"interpreter": null,
9+
"body": [
10+
{
11+
"type": "ExpressionStatement",
12+
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
13+
"expression": {
14+
"type": "TaggedTemplateExpression",
15+
"start":0,"end":21,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":21,"index":21}},
16+
"tag": {
17+
"type": "Identifier",
18+
"start":0,"end":3,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":3,"index":3},"identifierName":"tag"},
19+
"name": "tag"
20+
},
21+
"quasi": {
22+
"type": "TemplateLiteral",
23+
"start":3,"end":21,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":21,"index":21}},
24+
"expressions": [],
25+
"quasis": [
26+
{
27+
"type": "TemplateElement",
28+
"start":4,"end":20,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":20,"index":20}},
29+
"value": {
30+
"raw": "abc\\u{1000_0000}",
31+
"cooked": null
32+
},
33+
"tail": true
34+
}
35+
]
36+
}
37+
}
38+
}
39+
],
40+
"directives": []
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`abc\u{1000_0000}`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"type": "File",
3+
"start":0,"end":19,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":19,"index":19}},
4+
"errors": [
5+
"SyntaxError: Invalid escape sequence in template. (1:5)"
6+
],
7+
"program": {
8+
"type": "Program",
9+
"start":0,"end":19,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":19,"index":19}},
10+
"sourceType": "script",
11+
"interpreter": null,
12+
"body": [
13+
{
14+
"type": "ExpressionStatement",
15+
"start":0,"end":19,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":19,"index":19}},
16+
"expression": {
17+
"type": "TemplateLiteral",
18+
"start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}},
19+
"expressions": [],
20+
"quasis": [
21+
{
22+
"type": "TemplateElement",
23+
"start":1,"end":17,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":17,"index":17}},
24+
"value": {
25+
"raw": "abc\\u{1000_0000}",
26+
"cooked": null
27+
},
28+
"tail": true
29+
}
30+
]
31+
}
32+
}
33+
],
34+
"directives": []
35+
}
36+
}

packages/babel-types/src/definitions/core.ts

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,40 +1979,33 @@ defineType("TemplateElement", {
19791979
function templateElementCookedValidator(node: t.TemplateElement) {
19801980
const raw = node.value.raw;
19811981

1982-
let str,
1983-
containsInvalid,
1984-
unterminatedCalled = false;
1985-
try {
1986-
const error = () => {
1987-
throw new Error();
1988-
};
1989-
({ str, containsInvalid } = readStringContents(
1990-
"template",
1991-
raw,
1992-
0,
1993-
0,
1994-
0,
1995-
{
1996-
unterminated() {
1997-
unterminatedCalled = true;
1998-
},
1999-
strictNumericEscape: error,
2000-
invalidEscapeSequence: error,
2001-
numericSeparatorInEscapeSequence: error,
2002-
unexpectedNumericSeparator: error,
2003-
invalidDigit: error,
2004-
invalidCodePoint: error,
1982+
let unterminatedCalled = false;
1983+
1984+
const error = () => {
1985+
// unreachable
1986+
throw new Error("Internal @babel/types error.");
1987+
};
1988+
const { str, firstInvalidLoc } = readStringContents(
1989+
"template",
1990+
raw,
1991+
0,
1992+
0,
1993+
0,
1994+
{
1995+
unterminated() {
1996+
unterminatedCalled = true;
20051997
},
2006-
));
2007-
} catch {
2008-
// TODO: When https://github.com/babel/babel/issues/14775 is fixed
2009-
// we can remove the try/catch block.
2010-
unterminatedCalled = true;
2011-
containsInvalid = true;
2012-
}
1998+
strictNumericEscape: error,
1999+
invalidEscapeSequence: error,
2000+
numericSeparatorInEscapeSequence: error,
2001+
unexpectedNumericSeparator: error,
2002+
invalidDigit: error,
2003+
invalidCodePoint: error,
2004+
},
2005+
);
20132006
if (!unterminatedCalled) throw new Error("Invalid raw");
20142007

2015-
node.value.cooked = containsInvalid ? null : str;
2008+
node.value.cooked = firstInvalidLoc ? null : str;
20162009
},
20172010
),
20182011
},

0 commit comments

Comments
 (0)