diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 5cca456dd4905..7c5d6bb6b1c80 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 4c8a5e567f881..6451a4f5c9669 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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); } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 44a289161859d..75f0b31c8960e 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -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(); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b04cac4714395..c1e51c580a5d8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -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 diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 02ca57919c969..5c03ad7fe5fcf 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2639,6 +2639,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); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3ed8570cab81c..4c88e5fe219ca 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3174,6 +3174,7 @@ declare namespace ts { getTokenPos(): number; getTokenText(): string; getTokenValue(): string; + hasUnicodeEscape(): boolean; hasExtendedUnicodeEscape(): boolean; hasPrecedingLineBreak(): boolean; isIdentifier(): boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3b2ad792601b9..bb30ccc908dd1 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3174,6 +3174,7 @@ declare namespace ts { getTokenPos(): number; getTokenText(): string; getTokenValue(): string; + hasUnicodeEscape(): boolean; hasExtendedUnicodeEscape(): boolean; hasPrecedingLineBreak(): boolean; isIdentifier(): boolean; diff --git a/tests/baselines/reference/scannerUnicodeEscapeInKeyword1.errors.txt b/tests/baselines/reference/scannerUnicodeEscapeInKeyword1.errors.txt new file mode 100644 index 0000000000000..ec7c0033271ca --- /dev/null +++ b/tests/baselines/reference/scannerUnicodeEscapeInKeyword1.errors.txt @@ -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. \ No newline at end of file diff --git a/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.errors.txt b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.errors.txt new file mode 100644 index 0000000000000..f65d70920c540 --- /dev/null +++ b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.errors.txt @@ -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 + \ No newline at end of file diff --git a/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.js b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.js new file mode 100644 index 0000000000000..d93ba71d1d312 --- /dev/null +++ b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.js @@ -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 diff --git a/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.symbols b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.symbols new file mode 100644 index 0000000000000..bd6c92ec26dae --- /dev/null +++ b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.symbols @@ -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 + diff --git a/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.types b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.types new file mode 100644 index 0000000000000..e4460866a51b5 --- /dev/null +++ b/tests/baselines/reference/scannerUnicodeEscapeInKeyword2.types @@ -0,0 +1,77 @@ +=== tests/cases/conformance/scanner/ecmascript5/file1.ts === +var \u0061wait = 12; // ok +>\u0061wait : number +>12 : 12 + +async function main() { +>main : () => Promise + + \u0061wait 12; // not ok +>\u0061wait 12 : 12 +>12 : 12 +} + +var \u0079ield = 12; // ok +>\u0079ield : number +>12 : 12 + +function *gen() { +>gen : () => Generator + + \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 + + \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 + + \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 + diff --git a/tests/baselines/reference/switchStatementsWithMultipleDefaults.errors.txt b/tests/baselines/reference/switchStatementsWithMultipleDefaults.errors.txt index 9760ce4d99ba7..6556585789410 100644 --- a/tests/baselines/reference/switchStatementsWithMultipleDefaults.errors.txt +++ b/tests/baselines/reference/switchStatementsWithMultipleDefaults.errors.txt @@ -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: } } \ No newline at end of file diff --git a/tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword2.ts b/tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword2.ts new file mode 100644 index 0000000000000..a92c22569dba2 --- /dev/null +++ b/tests/cases/conformance/scanner/ecmascript5/scannerUnicodeEscapeInKeyword2.ts @@ -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