Skip to content

Commit 4ab85bb

Browse files
authored
Add error message for keywords with escapes in them (microsoft#32718)
* Add error message for keywords with escapes in them * Move check into parser during advance to next token to utilize context for contextual keywords * git add . * Add tests for extended escapes * Better error courtesy of @DanielRossenwaser * Add test of browser-inconsistent case and alter condition to match spec * Merge adjacent conditions * Use seperate functions for checking keywords vs not * Use flags to track unicode escape presence * Adjust error text
1 parent 51411c1 commit 4ab85bb

14 files changed

+341
-12
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,10 @@
851851
"category": "Error",
852852
"code": 1259
853853
},
854+
"Keywords cannot contain escape characters.": {
855+
"category": "Error",
856+
"code": 1260
857+
},
854858
"'with' statements are not allowed in an async function block.": {
855859
"category": "Error",
856860
"code": 1300

src/compiler/parser.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,10 +1086,19 @@ namespace ts {
10861086
return currentToken;
10871087
}
10881088

1089-
function nextToken(): SyntaxKind {
1089+
function nextTokenWithoutCheck() {
10901090
return currentToken = scanner.scan();
10911091
}
10921092

1093+
function nextToken(): SyntaxKind {
1094+
// if the keyword had an escape
1095+
if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) {
1096+
// issue a parse error for the escape
1097+
parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters);
1098+
}
1099+
return nextTokenWithoutCheck();
1100+
}
1101+
10931102
function nextTokenJSDoc(): JSDocSyntaxKind {
10941103
return currentToken = scanner.scanJsDocToken();
10951104
}
@@ -1380,7 +1389,7 @@ namespace ts {
13801389
node.originalKeywordKind = token();
13811390
}
13821391
node.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue()));
1383-
nextToken();
1392+
nextTokenWithoutCheck();
13841393
return finishNode(node);
13851394
}
13861395

src/compiler/scanner.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace ts {
1818
getTokenPos(): number;
1919
getTokenText(): string;
2020
getTokenValue(): string;
21+
hasUnicodeEscape(): boolean;
2122
hasExtendedUnicodeEscape(): boolean;
2223
hasPrecedingLineBreak(): boolean;
2324
isIdentifier(): boolean;
@@ -884,6 +885,7 @@ namespace ts {
884885
getTokenPos: () => tokenPos,
885886
getTokenText: () => text.substring(tokenPos, pos),
886887
getTokenValue: () => tokenValue,
888+
hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0,
887889
hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0,
888890
hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0,
889891
isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord,
@@ -1245,6 +1247,7 @@ namespace ts {
12451247
return scanExtendedUnicodeEscape();
12461248
}
12471249

1250+
tokenFlags |= TokenFlags.UnicodeEscape;
12481251
// '\uDDDD'
12491252
return scanHexadecimalEscape(/*numDigits*/ 4);
12501253

@@ -1376,6 +1379,7 @@ namespace ts {
13761379
if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) {
13771380
break;
13781381
}
1382+
tokenFlags |= TokenFlags.UnicodeEscape;
13791383
result += text.substring(start, pos);
13801384
result += utf16EncodeAsString(ch);
13811385
// Valid Unicode escape is always six characters
@@ -1868,6 +1872,7 @@ namespace ts {
18681872
const cookedChar = peekUnicodeEscape();
18691873
if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) {
18701874
pos += 6;
1875+
tokenFlags |= TokenFlags.UnicodeEscape;
18711876
tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts();
18721877
return token = getIdentifierToken();
18731878
}
@@ -2156,6 +2161,7 @@ namespace ts {
21562161
const cookedChar = peekUnicodeEscape();
21572162
if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) {
21582163
pos += 6;
2164+
tokenFlags |= TokenFlags.UnicodeEscape;
21592165
tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts();
21602166
return token = getIdentifierToken();
21612167
}

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,8 @@ namespace ts {
16791679
/* @internal */
16801680
ContainsSeparator = 1 << 9, // e.g. `0b1100_0101`
16811681
/* @internal */
1682+
UnicodeEscape = 1 << 10,
1683+
/* @internal */
16821684
BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
16831685
/* @internal */
16841686
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,6 +2682,10 @@ namespace ts {
26822682
return isKeyword(token) && !isContextualKeyword(token);
26832683
}
26842684

2685+
export function isFutureReservedKeyword(token: SyntaxKind): boolean {
2686+
return SyntaxKind.FirstFutureReservedWord <= token && token <= SyntaxKind.LastFutureReservedWord;
2687+
}
2688+
26852689
export function isStringANonContextualKeyword(name: string) {
26862690
const token = stringToToken(name);
26872691
return token !== undefined && isNonContextualKeyword(token);

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3174,6 +3174,7 @@ declare namespace ts {
31743174
getTokenPos(): number;
31753175
getTokenText(): string;
31763176
getTokenValue(): string;
3177+
hasUnicodeEscape(): boolean;
31773178
hasExtendedUnicodeEscape(): boolean;
31783179
hasPrecedingLineBreak(): boolean;
31793180
isIdentifier(): boolean;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3174,6 +3174,7 @@ declare namespace ts {
31743174
getTokenPos(): number;
31753175
getTokenText(): string;
31763176
getTokenValue(): string;
3177+
hasUnicodeEscape(): boolean;
31773178
hasExtendedUnicodeEscape(): boolean;
31783179
hasPrecedingLineBreak(): boolean;
31793180
isIdentifier(): boolean;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword1.ts(1,1): error TS1260: Keywords cannot contain escape characters.
2+
3+
4+
==== tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword1.ts (1 errors) ====
5+
\u0076ar x = "hello";
6+
~~~~~~~~
7+
!!! error TS1260: Keywords cannot contain escape characters.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
tests/cases/conformance/scanner/ecmascript5/file1.ts(3,5): error TS1260: Keywords cannot contain escape characters.
2+
tests/cases/conformance/scanner/ecmascript5/file1.ts(8,5): error TS1260: Keywords cannot contain escape characters.
3+
tests/cases/conformance/scanner/ecmascript5/file1.ts(13,1): error TS1260: Keywords cannot contain escape characters.
4+
tests/cases/conformance/scanner/ecmascript5/file2.ts(1,1): error TS1260: Keywords cannot contain escape characters.
5+
tests/cases/conformance/scanner/ecmascript5/file2.ts(5,5): error TS1260: Keywords cannot contain escape characters.
6+
tests/cases/conformance/scanner/ecmascript5/file2.ts(10,5): error TS1260: Keywords cannot contain escape characters.
7+
tests/cases/conformance/scanner/ecmascript5/file2.ts(15,1): error TS1260: Keywords cannot contain escape characters.
8+
9+
10+
==== tests/cases/conformance/scanner/ecmascript5/file1.ts (3 errors) ====
11+
var \u0061wait = 12; // ok
12+
async function main() {
13+
\u0061wait 12; // not ok
14+
~~~~~~~~~~
15+
!!! error TS1260: Keywords cannot contain escape characters.
16+
}
17+
18+
var \u0079ield = 12; // ok
19+
function *gen() {
20+
\u0079ield 12; //not ok
21+
~~~~~~~~~~
22+
!!! error TS1260: Keywords cannot contain escape characters.
23+
}
24+
25+
type typ\u0065 = 12; // ok
26+
27+
typ\u0065 notok = 0; // not ok
28+
~~~~~~~~~
29+
!!! error TS1260: Keywords cannot contain escape characters.
30+
31+
export {};
32+
==== tests/cases/conformance/scanner/ecmascript5/file2.ts (4 errors) ====
33+
\u{0076}ar x = "hello"; // not ok
34+
~~~~~~~~~~
35+
!!! error TS1260: Keywords cannot contain escape characters.
36+
37+
var \u{0061}wait = 12; // ok
38+
async function main() {
39+
\u{0061}wait 12; // not ok
40+
~~~~~~~~~~~~
41+
!!! error TS1260: Keywords cannot contain escape characters.
42+
}
43+
44+
var \u{0079}ield = 12; // ok
45+
function *gen() {
46+
\u{0079}ield 12; //not ok
47+
~~~~~~~~~~~~
48+
!!! error TS1260: Keywords cannot contain escape characters.
49+
}
50+
51+
type typ\u{0065} = 12; // ok
52+
53+
typ\u{0065} notok = 0; // not ok
54+
~~~~~~~~~~~
55+
!!! error TS1260: Keywords cannot contain escape characters.
56+
57+
export {};
58+
59+
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
60+
// chrome and jsc may still error on this, ref https://bugs.chromium.org/p/chromium/issues/detail?id=993000 and https://bugs.webkit.org/show_bug.cgi?id=200638
61+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//// [tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword2.ts] ////
2+
3+
//// [file1.ts]
4+
var \u0061wait = 12; // ok
5+
async function main() {
6+
\u0061wait 12; // not ok
7+
}
8+
9+
var \u0079ield = 12; // ok
10+
function *gen() {
11+
\u0079ield 12; //not ok
12+
}
13+
14+
type typ\u0065 = 12; // ok
15+
16+
typ\u0065 notok = 0; // not ok
17+
18+
export {};
19+
//// [file2.ts]
20+
\u{0076}ar x = "hello"; // not ok
21+
22+
var \u{0061}wait = 12; // ok
23+
async function main() {
24+
\u{0061}wait 12; // not ok
25+
}
26+
27+
var \u{0079}ield = 12; // ok
28+
function *gen() {
29+
\u{0079}ield 12; //not ok
30+
}
31+
32+
type typ\u{0065} = 12; // ok
33+
34+
typ\u{0065} notok = 0; // not ok
35+
36+
export {};
37+
38+
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
39+
// chrome and jsc may still error on this, ref https://bugs.chromium.org/p/chromium/issues/detail?id=993000 and https://bugs.webkit.org/show_bug.cgi?id=200638
40+
41+
42+
//// [file1.js]
43+
var \u0061wait = 12; // ok
44+
async function main() {
45+
await 12; // not ok
46+
}
47+
var \u0079ield = 12; // ok
48+
function* gen() {
49+
yield 12; //not ok
50+
}
51+
//// [file2.js]
52+
var x = "hello"; // not ok
53+
var \u{0061}wait = 12; // ok
54+
async function main() {
55+
await 12; // not ok
56+
}
57+
var \u{0079}ield = 12; // ok
58+
function* gen() {
59+
yield 12; //not ok
60+
}
61+
const a = { def\u0061ult: 12 }; // OK, `default` not in keyword position
62+
// chrome and jsc may still error on this, ref https://bugs.chromium.org/p/chromium/issues/detail?id=993000 and https://bugs.webkit.org/show_bug.cgi?id=200638
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
=== tests/cases/conformance/scanner/ecmascript5/file1.ts ===
2+
var \u0061wait = 12; // ok
3+
>\u0061wait : Symbol(\u0061wait, Decl(file1.ts, 0, 3))
4+
5+
async function main() {
6+
>main : Symbol(main, Decl(file1.ts, 0, 20))
7+
8+
\u0061wait 12; // not ok
9+
}
10+
11+
var \u0079ield = 12; // ok
12+
>\u0079ield : Symbol(\u0079ield, Decl(file1.ts, 5, 3))
13+
14+
function *gen() {
15+
>gen : Symbol(gen, Decl(file1.ts, 5, 20))
16+
17+
\u0079ield 12; //not ok
18+
}
19+
20+
type typ\u0065 = 12; // ok
21+
>typ\u0065 : Symbol(typ\u0065, Decl(file1.ts, 8, 1))
22+
23+
typ\u0065 notok = 0; // not ok
24+
>notok : Symbol(notok, Decl(file1.ts, 10, 20))
25+
26+
export {};
27+
=== tests/cases/conformance/scanner/ecmascript5/file2.ts ===
28+
\u{0076}ar x = "hello"; // not ok
29+
>x : Symbol(x, Decl(file2.ts, 0, 10))
30+
31+
var \u{0061}wait = 12; // ok
32+
>\u{0061}wait : Symbol(\u{0061}wait, Decl(file2.ts, 2, 3))
33+
34+
async function main() {
35+
>main : Symbol(main, Decl(file2.ts, 2, 22))
36+
37+
\u{0061}wait 12; // not ok
38+
}
39+
40+
var \u{0079}ield = 12; // ok
41+
>\u{0079}ield : Symbol(\u{0079}ield, Decl(file2.ts, 7, 3))
42+
43+
function *gen() {
44+
>gen : Symbol(gen, Decl(file2.ts, 7, 22))
45+
46+
\u{0079}ield 12; //not ok
47+
}
48+
49+
type typ\u{0065} = 12; // ok
50+
>typ\u{0065} : Symbol(typ\u{0065}, Decl(file2.ts, 10, 1))
51+
52+
typ\u{0065} notok = 0; // not ok
53+
>notok : Symbol(notok, Decl(file2.ts, 12, 22))
54+
55+
export {};
56+
57+
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
58+
>a : Symbol(a, Decl(file2.ts, 18, 5))
59+
>def\u0061ult : Symbol(def\u0061ult, Decl(file2.ts, 18, 11))
60+
61+
// chrome and jsc may still error on this, ref https://bugs.chromium.org/p/chromium/issues/detail?id=993000 and https://bugs.webkit.org/show_bug.cgi?id=200638
62+

0 commit comments

Comments
 (0)