Add error message for keywords with escapes in them (#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
This commit is contained in:
Wesley Wigham 2019-08-12 16:00:38 -07:00 committed by GitHub
parent 51411c1f8b
commit 4ab85bbf35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 341 additions and 12 deletions

View file

@ -851,6 +851,10 @@
"category": "Error",
"code": 1259
},
"Keywords cannot contain escape characters.": {
"category": "Error",
"code": 1260
},
"'with' statements are not allowed in an async function block.": {
"category": "Error",
"code": 1300

View file

@ -1086,10 +1086,19 @@ namespace ts {
return currentToken;
}
function nextToken(): SyntaxKind {
function nextTokenWithoutCheck() {
return currentToken = scanner.scan();
}
function nextToken(): SyntaxKind {
// if the keyword had an escape
if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) {
// issue a parse error for the escape
parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters);
}
return nextTokenWithoutCheck();
}
function nextTokenJSDoc(): JSDocSyntaxKind {
return currentToken = scanner.scanJsDocToken();
}
@ -1380,7 +1389,7 @@ namespace ts {
node.originalKeywordKind = token();
}
node.escapedText = escapeLeadingUnderscores(internIdentifier(scanner.getTokenValue()));
nextToken();
nextTokenWithoutCheck();
return finishNode(node);
}

View file

@ -18,6 +18,7 @@ namespace ts {
getTokenPos(): number;
getTokenText(): string;
getTokenValue(): string;
hasUnicodeEscape(): boolean;
hasExtendedUnicodeEscape(): boolean;
hasPrecedingLineBreak(): boolean;
isIdentifier(): boolean;
@ -884,6 +885,7 @@ namespace ts {
getTokenPos: () => tokenPos,
getTokenText: () => text.substring(tokenPos, pos),
getTokenValue: () => tokenValue,
hasUnicodeEscape: () => (tokenFlags & TokenFlags.UnicodeEscape) !== 0,
hasExtendedUnicodeEscape: () => (tokenFlags & TokenFlags.ExtendedUnicodeEscape) !== 0,
hasPrecedingLineBreak: () => (tokenFlags & TokenFlags.PrecedingLineBreak) !== 0,
isIdentifier: () => token === SyntaxKind.Identifier || token > SyntaxKind.LastReservedWord,
@ -1245,6 +1247,7 @@ namespace ts {
return scanExtendedUnicodeEscape();
}
tokenFlags |= TokenFlags.UnicodeEscape;
// '\uDDDD'
return scanHexadecimalEscape(/*numDigits*/ 4);
@ -1376,6 +1379,7 @@ namespace ts {
if (!(ch >= 0 && isIdentifierPart(ch, languageVersion))) {
break;
}
tokenFlags |= TokenFlags.UnicodeEscape;
result += text.substring(start, pos);
result += utf16EncodeAsString(ch);
// Valid Unicode escape is always six characters
@ -1868,6 +1872,7 @@ namespace ts {
const cookedChar = peekUnicodeEscape();
if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) {
pos += 6;
tokenFlags |= TokenFlags.UnicodeEscape;
tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts();
return token = getIdentifierToken();
}
@ -2156,6 +2161,7 @@ namespace ts {
const cookedChar = peekUnicodeEscape();
if (cookedChar >= 0 && isIdentifierStart(cookedChar, languageVersion)) {
pos += 6;
tokenFlags |= TokenFlags.UnicodeEscape;
tokenValue = String.fromCharCode(cookedChar) + scanIdentifierParts();
return token = getIdentifierToken();
}

View file

@ -1679,6 +1679,8 @@ namespace ts {
/* @internal */
ContainsSeparator = 1 << 9, // e.g. `0b1100_0101`
/* @internal */
UnicodeEscape = 1 << 10,
/* @internal */
BinaryOrOctalSpecifier = BinarySpecifier | OctalSpecifier,
/* @internal */
NumericLiteralFlags = Scientific | Octal | HexSpecifier | BinaryOrOctalSpecifier | ContainsSeparator

View file

@ -2682,6 +2682,10 @@ namespace ts {
return isKeyword(token) && !isContextualKeyword(token);
}
export function isFutureReservedKeyword(token: SyntaxKind): boolean {
return SyntaxKind.FirstFutureReservedWord <= token && token <= SyntaxKind.LastFutureReservedWord;
}
export function isStringANonContextualKeyword(name: string) {
const token = stringToToken(name);
return token !== undefined && isNonContextualKeyword(token);

View file

@ -3174,6 +3174,7 @@ declare namespace ts {
getTokenPos(): number;
getTokenText(): string;
getTokenValue(): string;
hasUnicodeEscape(): boolean;
hasExtendedUnicodeEscape(): boolean;
hasPrecedingLineBreak(): boolean;
isIdentifier(): boolean;

View file

@ -3174,6 +3174,7 @@ declare namespace ts {
getTokenPos(): number;
getTokenText(): string;
getTokenValue(): string;
hasUnicodeEscape(): boolean;
hasExtendedUnicodeEscape(): boolean;
hasPrecedingLineBreak(): boolean;
isIdentifier(): boolean;

View file

@ -0,0 +1,7 @@
tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword1.ts(1,1): error TS1260: Keywords cannot contain escape characters.
==== tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword1.ts (1 errors) ====
\u0076ar x = "hello";
~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.

View file

@ -0,0 +1,61 @@
tests/cases/conformance/scanner/ecmascript5/file1.ts(3,5): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file1.ts(8,5): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file1.ts(13,1): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file2.ts(1,1): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file2.ts(5,5): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file2.ts(10,5): error TS1260: Keywords cannot contain escape characters.
tests/cases/conformance/scanner/ecmascript5/file2.ts(15,1): error TS1260: Keywords cannot contain escape characters.
==== tests/cases/conformance/scanner/ecmascript5/file1.ts (3 errors) ====
var \u0061wait = 12; // ok
async function main() {
\u0061wait 12; // not ok
~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
}
var \u0079ield = 12; // ok
function *gen() {
\u0079ield 12; //not ok
~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
}
type typ\u0065 = 12; // ok
typ\u0065 notok = 0; // not ok
~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
export {};
==== tests/cases/conformance/scanner/ecmascript5/file2.ts (4 errors) ====
\u{0076}ar x = "hello"; // not ok
~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
var \u{0061}wait = 12; // ok
async function main() {
\u{0061}wait 12; // not ok
~~~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
}
var \u{0079}ield = 12; // ok
function *gen() {
\u{0079}ield 12; //not ok
~~~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
}
type typ\u{0065} = 12; // ok
typ\u{0065} notok = 0; // not ok
~~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
export {};
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
// 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

View file

@ -0,0 +1,62 @@
//// [tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword2.ts] ////
//// [file1.ts]
var \u0061wait = 12; // ok
async function main() {
\u0061wait 12; // not ok
}
var \u0079ield = 12; // ok
function *gen() {
\u0079ield 12; //not ok
}
type typ\u0065 = 12; // ok
typ\u0065 notok = 0; // not ok
export {};
//// [file2.ts]
\u{0076}ar x = "hello"; // not ok
var \u{0061}wait = 12; // ok
async function main() {
\u{0061}wait 12; // not ok
}
var \u{0079}ield = 12; // ok
function *gen() {
\u{0079}ield 12; //not ok
}
type typ\u{0065} = 12; // ok
typ\u{0065} notok = 0; // not ok
export {};
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
// 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
//// [file1.js]
var \u0061wait = 12; // ok
async function main() {
await 12; // not ok
}
var \u0079ield = 12; // ok
function* gen() {
yield 12; //not ok
}
//// [file2.js]
var x = "hello"; // not ok
var \u{0061}wait = 12; // ok
async function main() {
await 12; // not ok
}
var \u{0079}ield = 12; // ok
function* gen() {
yield 12; //not ok
}
const a = { def\u0061ult: 12 }; // OK, `default` not in keyword position
// 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

View file

@ -0,0 +1,62 @@
=== tests/cases/conformance/scanner/ecmascript5/file1.ts ===
var \u0061wait = 12; // ok
>\u0061wait : Symbol(\u0061wait, Decl(file1.ts, 0, 3))
async function main() {
>main : Symbol(main, Decl(file1.ts, 0, 20))
\u0061wait 12; // not ok
}
var \u0079ield = 12; // ok
>\u0079ield : Symbol(\u0079ield, Decl(file1.ts, 5, 3))
function *gen() {
>gen : Symbol(gen, Decl(file1.ts, 5, 20))
\u0079ield 12; //not ok
}
type typ\u0065 = 12; // ok
>typ\u0065 : Symbol(typ\u0065, Decl(file1.ts, 8, 1))
typ\u0065 notok = 0; // not ok
>notok : Symbol(notok, Decl(file1.ts, 10, 20))
export {};
=== tests/cases/conformance/scanner/ecmascript5/file2.ts ===
\u{0076}ar x = "hello"; // not ok
>x : Symbol(x, Decl(file2.ts, 0, 10))
var \u{0061}wait = 12; // ok
>\u{0061}wait : Symbol(\u{0061}wait, Decl(file2.ts, 2, 3))
async function main() {
>main : Symbol(main, Decl(file2.ts, 2, 22))
\u{0061}wait 12; // not ok
}
var \u{0079}ield = 12; // ok
>\u{0079}ield : Symbol(\u{0079}ield, Decl(file2.ts, 7, 3))
function *gen() {
>gen : Symbol(gen, Decl(file2.ts, 7, 22))
\u{0079}ield 12; //not ok
}
type typ\u{0065} = 12; // ok
>typ\u{0065} : Symbol(typ\u{0065}, Decl(file2.ts, 10, 1))
typ\u{0065} notok = 0; // not ok
>notok : Symbol(notok, Decl(file2.ts, 12, 22))
export {};
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
>a : Symbol(a, Decl(file2.ts, 18, 5))
>def\u0061ult : Symbol(def\u0061ult, Decl(file2.ts, 18, 11))
// 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

View file

@ -0,0 +1,77 @@
=== tests/cases/conformance/scanner/ecmascript5/file1.ts ===
var \u0061wait = 12; // ok
>\u0061wait : number
>12 : 12
async function main() {
>main : () => Promise<void>
\u0061wait 12; // not ok
>\u0061wait 12 : 12
>12 : 12
}
var \u0079ield = 12; // ok
>\u0079ield : number
>12 : 12
function *gen() {
>gen : () => Generator<number, void, unknown>
\u0079ield 12; //not ok
>\u0079ield 12 : any
>12 : 12
}
type typ\u0065 = 12; // ok
>typ\u0065 : 12
typ\u0065 notok = 0; // not ok
>notok : 0
export {};
=== tests/cases/conformance/scanner/ecmascript5/file2.ts ===
\u{0076}ar x = "hello"; // not ok
>x : string
>"hello" : "hello"
var \u{0061}wait = 12; // ok
>\u{0061}wait : number
>12 : 12
async function main() {
>main : () => Promise<void>
\u{0061}wait 12; // not ok
>\u{0061}wait 12 : 12
>12 : 12
}
var \u{0079}ield = 12; // ok
>\u{0079}ield : number
>12 : 12
function *gen() {
>gen : () => Generator<number, void, unknown>
\u{0079}ield 12; //not ok
>\u{0079}ield 12 : any
>12 : 12
}
type typ\u{0065} = 12; // ok
>typ\u{0065} : 12
typ\u{0065} notok = 0; // not ok
>notok : 0
export {};
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
>a : { def\u0061ult: number; }
>{def\u0061ult: 12} : { def\u0061ult: number; }
>def\u0061ult : number
>12 : 12
// 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

View file

@ -1,9 +1,7 @@
tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(8,5): error TS1113: A 'default' clause cannot appear more than once in a 'switch' statement.
tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(20,13): error TS1113: A 'default' clause cannot appear more than once in a 'switch' statement.
tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(27,22): error TS1108: A 'return' statement can only be used within a function body.
tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(25,13): error TS1260: Keywords cannot contain escape characters.
==== tests/cases/compiler/switchStatementsWithMultipleDefaults.ts (3 errors) ====
==== tests/cases/compiler/switchStatementsWithMultipleDefaults.ts (1 errors) ====
var x = 10;
switch (x) {
@ -12,8 +10,6 @@ tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(27,22): error TS110
default: // No issues.
break;
default: // Error; second 'default' clause.
~~~~~~~~
!!! error TS1113: A 'default' clause cannot appear more than once in a 'switch' statement.
default: // Error; third 'default' clause.
case 3:
x *= x;
@ -26,17 +22,15 @@ tests/cases/compiler/switchStatementsWithMultipleDefaults.ts(27,22): error TS110
switch (x * x) {
default: // No issues.
default: // Error; second 'default' clause.
~~~~~~~~
!!! error TS1113: A 'default' clause cannot appear more than once in a 'switch' statement.
break;
case 10000:
x /= x;
default: // Error, third 'default' clause
def\u0061ult: // Error, fourth 'default' clause.
~~~~~~~~~~~~
!!! error TS1260: Keywords cannot contain escape characters.
// Errors on fifth-seventh
default: return;
~~~~~~
!!! error TS1108: A 'return' statement can only be used within a function body.
default: default:
}
}

View file

@ -0,0 +1,39 @@
// @target: esnext
// @filename: file1.ts
var \u0061wait = 12; // ok
async function main() {
\u0061wait 12; // not ok
}
var \u0079ield = 12; // ok
function *gen() {
\u0079ield 12; //not ok
}
type typ\u0065 = 12; // ok
typ\u0065 notok = 0; // not ok
export {};
// @filename: file2.ts
\u{0076}ar x = "hello"; // not ok
var \u{0061}wait = 12; // ok
async function main() {
\u{0061}wait 12; // not ok
}
var \u{0079}ield = 12; // ok
function *gen() {
\u{0079}ield 12; //not ok
}
type typ\u{0065} = 12; // ok
typ\u{0065} notok = 0; // not ok
export {};
const a = {def\u0061ult: 12}; // OK, `default` not in keyword position
// 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