From d0ef028841a286d23870940e239760aa2fc3c1e2 Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Sat, 25 May 2024 03:46:26 +0800 Subject: [PATCH] Improve Recovery of Unterminated Regular Expressions (#58289) Co-authored-by: Ron Buckton --- src/compiler/checker.ts | 2 +- src/compiler/parser.ts | 7 +- src/compiler/scanner.ts | 172 ++++++++++-------- src/compiler/types.ts | 22 +-- src/testRunner/tests.ts | 1 + src/testRunner/unittests/incrementalParser.ts | 2 +- .../unittests/regExpScannerRecovery.ts | 81 +++++++++ .../reference/parser645086_1.errors.txt | 7 +- .../reference/parser645086_2.errors.txt | 7 +- .../reference/parserMissingToken2.errors.txt | 4 +- .../reference/parserMissingToken2.js | 2 +- .../reference/parserMissingToken2.types | 4 +- ...gularExpressionDivideAmbiguity4.errors.txt | 11 +- ...parserRegularExpressionDivideAmbiguity4.js | 2 +- ...serRegularExpressionDivideAmbiguity4.types | 8 +- .../tsxAttributeInvalidNames.errors.txt | 4 +- .../reference/tsxAttributeInvalidNames.js | 2 +- .../reference/tsxAttributeInvalidNames.types | 4 +- ...unterminatedRegexAtEndOfSource1.errors.txt | 4 +- tests/cases/fourslash/whiteSpaceTrimming4.ts | 2 +- 20 files changed, 223 insertions(+), 125 deletions(-) create mode 100644 src/testRunner/unittests/regExpScannerRecovery.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 85c437de9be46..a9830b1264457 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -31974,7 +31974,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { scanner.setScriptTarget(sourceFile.languageVersion); scanner.setLanguageVariant(sourceFile.languageVariant); scanner.setOnError((message, length, arg0) => { - // emulate `parseErrorAtPosition` from parser.ts + // For providing spelling suggestions const start = scanner!.getTokenEnd(); if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) { const error = createDetachedDiagnostic(sourceFile.fileName, sourceFile.text, start, length, message, arg0); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index a63735ad6bfa2..56b958bcf3b74 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -62,7 +62,6 @@ import { DeleteExpression, Diagnostic, DiagnosticArguments, - DiagnosticCategory, DiagnosticMessage, Diagnostics, DiagnosticWithDetachedLocation, @@ -2144,11 +2143,7 @@ namespace Parser { // Don't report another error if it would just be at the same position as the last error. const lastError = lastOrUndefined(parseDiagnostics); let result: DiagnosticWithDetachedLocation | undefined; - if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) { - result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args); - addRelatedInfo(lastError, result); - } - else if (!lastError || start !== lastError.start) { + if (!lastError || start !== lastError.start) { result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args); parseDiagnostics.push(result); } diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 5d93e57ebe660..1279d5a8b2e84 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -1613,7 +1613,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean isRegularExpression && shouldEmitInvalidEscapeError && escapedValue >= 0xD800 && escapedValue <= 0xDBFF && pos + 6 < end && text.substring(pos, pos + 2) === "\\u" && charCodeUnchecked(pos + 2) !== CharacterCodes.openBrace ) { - // For regular expressions in Unicode mode, \u HexLeadSurrogate \u HexTrailSurrogate is treated as a single character + // For regular expressions in any Unicode mode, \u HexLeadSurrogate \u HexTrailSurrogate is treated as a single character // for the purpose of determining whether a character class range is out of order // https://tc39.es/ecma262/#prod-RegExpUnicodeEscapeSequence const nextStart = pos; @@ -2424,10 +2424,11 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean function reScanSlashToken(reportErrors?: boolean): SyntaxKind { if (token === SyntaxKind.SlashToken || token === SyntaxKind.SlashEqualsToken) { // Quickly get to the end of regex such that we know the flags - let p = tokenStart + 1; + const startOfRegExpBody = tokenStart + 1; + pos = startOfRegExpBody; let inEscape = false; // Although nested character classes are allowed in Unicode Sets mode, - // an unescaped slash is nevertheless invalid even in a character class in Unicode mode. + // an unescaped slash is nevertheless invalid even in a character class in any Unicode mode. // Additionally, parsing nested character classes will misinterpret regexes like `/[[]/` // as unterminated, consuming characters beyond the slash. (This even applies to `/[[]/v`, // which should be parsed as a well-terminated regex with an incomplete character class.) @@ -2436,16 +2437,9 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean while (true) { // If we reach the end of a file, or hit a newline, then this is an unterminated // regex. Report error and return what we have so far. - if (p >= end) { - tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); - break; - } - - const ch = charCodeUnchecked(p); - if (isLineBreak(ch)) { + const ch = charCodeChecked(pos); + if (ch === CharacterCodes.EOF || isLineBreak(ch)) { tokenFlags |= TokenFlags.Unterminated; - error(Diagnostics.Unterminated_regular_expression_literal); break; } @@ -2457,7 +2451,6 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean else if (ch === CharacterCodes.slash && !inCharacterClass) { // A slash within a character class is permissible, // but in general it signals the end of the regexp literal. - p++; break; } else if (ch === CharacterCodes.openBracket) { @@ -2469,47 +2462,89 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean else if (ch === CharacterCodes.closeBracket) { inCharacterClass = false; } - p++; + pos++; } - const isUnterminated = !!(tokenFlags & TokenFlags.Unterminated); - const endOfBody = p - (isUnterminated ? 0 : 1); - let regExpFlags = RegularExpressionFlags.None; - while (p < end) { - const ch = charCodeUnchecked(p); - if (!isIdentifierPart(ch, languageVersion)) { - break; - } - if (reportErrors) { - const flag = characterToRegularExpressionFlag(String.fromCharCode(ch)); - if (flag === undefined) { - error(Diagnostics.Unknown_regular_expression_flag, p, 1); + const endOfRegExpBody = pos; + if (tokenFlags & TokenFlags.Unterminated) { + // Search for the nearest unbalanced bracket for better recovery. Since the expression is + // invalid anyways, we take nested square brackets into consideration for the best guess. + pos = startOfRegExpBody; + inEscape = false; + let characterClassDepth = 0; + let inDecimalQuantifier = false; + let groupDepth = 0; + while (pos < endOfRegExpBody) { + const ch = charCodeUnchecked(pos); + if (inEscape) { + inEscape = false; + } + else if (ch === CharacterCodes.backslash) { + inEscape = true; + } + else if (ch === CharacterCodes.openBracket) { + characterClassDepth++; + } + else if (ch === CharacterCodes.closeBracket && characterClassDepth) { + characterClassDepth--; + } + else if (!characterClassDepth) { + if (ch === CharacterCodes.openBrace) { + inDecimalQuantifier = true; + } + else if (ch === CharacterCodes.closeBrace && inDecimalQuantifier) { + inDecimalQuantifier = false; + } + else if (!inDecimalQuantifier) { + if (ch === CharacterCodes.openParen) { + groupDepth++; + } + else if (ch === CharacterCodes.closeParen && groupDepth) { + groupDepth--; + } + else if (ch === CharacterCodes.closeParen || ch === CharacterCodes.closeBracket || ch === CharacterCodes.closeBrace) { + // We encountered an unbalanced bracket outside a character class. Treat this position as the end of regex. + break; + } + } } - else if (regExpFlags & flag) { - error(Diagnostics.Duplicate_regular_expression_flag, p, 1); + pos++; + } + // Whitespaces and semicolons at the end are not likely to be part of the regex + while (isWhiteSpaceLike(charCodeChecked(pos - 1)) || charCodeChecked(pos - 1) === CharacterCodes.semicolon) pos--; + error(Diagnostics.Unterminated_regular_expression_literal, tokenStart, pos - tokenStart); + } + else { + // Consume the slash character + pos++; + let regExpFlags = RegularExpressionFlags.None; + while (true) { + const ch = codePointChecked(pos); + if (ch === CharacterCodes.EOF || !isIdentifierPart(ch, languageVersion)) { + break; } - else if (((regExpFlags | flag) & RegularExpressionFlags.UnicodeMode) === RegularExpressionFlags.UnicodeMode) { - error(Diagnostics.The_Unicode_u_flag_and_the_Unicode_Sets_v_flag_cannot_be_set_simultaneously, p, 1); + if (reportErrors) { + const flag = characterToRegularExpressionFlag(String.fromCharCode(ch)); + if (flag === undefined) { + error(Diagnostics.Unknown_regular_expression_flag, pos, 1); + } + else if (regExpFlags & flag) { + error(Diagnostics.Duplicate_regular_expression_flag, pos, 1); + } + else if (((regExpFlags | flag) & RegularExpressionFlags.AnyUnicodeMode) === RegularExpressionFlags.AnyUnicodeMode) { + error(Diagnostics.The_Unicode_u_flag_and_the_Unicode_Sets_v_flag_cannot_be_set_simultaneously, pos, 1); + } + else { + regExpFlags |= flag; + checkRegularExpressionFlagAvailable(flag, pos); + } } - else { - regExpFlags |= flag; - checkRegularExpressionFlagAvailable(flag, p); - } - } - p++; - } - pos = p; - if (reportErrors) { - const saveTokenStart = tokenStart; - const saveTokenFlags = tokenFlags; - const savePos = pos; - const saveEnd = end; - pos = tokenStart + 1; - end = endOfBody; - scanRegularExpressionWorker(regExpFlags, isUnterminated, /*annexB*/ true); - tokenStart = saveTokenStart; - tokenFlags = saveTokenFlags; - pos = savePos; - end = saveEnd; + pos++; + } + if (reportErrors) { + scanRange(startOfRegExpBody, endOfRegExpBody - startOfRegExpBody, () => { + scanRegularExpressionWorker(regExpFlags, /*annexB*/ true); + }); + } } tokenValue = text.substring(tokenStart, pos); token = SyntaxKind.RegularExpressionLiteral; @@ -2517,7 +2552,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean return token; } - function scanRegularExpressionWorker(regExpFlags: RegularExpressionFlags, isUnterminated: boolean, annexB: boolean) { + function scanRegularExpressionWorker(regExpFlags: RegularExpressionFlags, annexB: boolean) { // Why var? It avoids TDZ checks in the runtime which can be costly. // See: https://github.com/microsoft/TypeScript/issues/52924 /* eslint-disable no-var */ @@ -2525,9 +2560,9 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean /** Grammar parameter */ var unicodeSetsMode = !!(regExpFlags & RegularExpressionFlags.UnicodeSets); /** Grammar parameter */ - var unicodeMode = !!(regExpFlags & RegularExpressionFlags.UnicodeMode); + var anyUnicodeMode = !!(regExpFlags & RegularExpressionFlags.AnyUnicodeMode); - if (unicodeMode) { + if (anyUnicodeMode) { // Annex B treats any unicode mode as the strict syntax. annexB = false; } @@ -2535,7 +2570,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean /** @see {scanClassSetExpression} */ var mayContainStrings = false; - /** The number of numeric (anonymous) capturing groups defined in the regex. */ + /** The number of all (named and unnamed) capturing groups defined in the regex. */ var numberOfCapturingGroups = 0; /** All named capturing groups defined in the regex. */ var groupSpecifiers: Set | undefined; @@ -2684,7 +2719,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean error(Diagnostics.Incomplete_quantifier_Digit_expected, digitsStart, 0); } else { - if (unicodeMode) { + if (anyUnicodeMode) { error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, start, 1, String.fromCharCode(ch)); } isPreviousTermQuantifiable = true; @@ -2696,7 +2731,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } } else if (!min) { - if (unicodeMode) { + if (anyUnicodeMode) { error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, start, 1, String.fromCharCode(ch)); } isPreviousTermQuantifiable = true; @@ -2740,11 +2775,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean // falls through case CharacterCodes.closeBracket: case CharacterCodes.closeBrace: - if (isUnterminated && !isInGroup) { - // Assume what starting from the character to be outside of the regex - return; - } - if (unicodeMode || ch === CharacterCodes.closeParen) { + if (anyUnicodeMode || ch === CharacterCodes.closeParen) { error(Diagnostics.Unexpected_0_Did_you_mean_to_escape_it_with_backslash, pos, 1, String.fromCharCode(ch)); } pos++; @@ -2801,7 +2832,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean scanGroupName(/*isReference*/ true); scanExpectedChar(CharacterCodes.greaterThan); } - else if (unicodeMode) { + else if (anyUnicodeMode) { error(Diagnostics.k_must_be_followed_by_a_capturing_group_name_enclosed_in_angle_brackets, pos - 2, 2); } break; @@ -2844,6 +2875,9 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean Debug.assertEqual(charCodeUnchecked(pos - 1), CharacterCodes.backslash); let ch = charCodeChecked(pos); switch (ch) { + case CharacterCodes.EOF: + error(Diagnostics.Undetermined_character_escape, pos - 1, 1); + return "\\"; case CharacterCodes.c: pos++; ch = charCodeChecked(pos); @@ -2851,7 +2885,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean pos++; return String.fromCharCode(ch & 0x1f); } - if (unicodeMode) { + if (anyUnicodeMode) { error(Diagnostics.c_must_be_followed_by_an_ASCII_letter, pos - 2, 2); } else if (atomEscape && annexB) { @@ -2882,12 +2916,8 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean pos++; return String.fromCharCode(ch); default: - if (pos >= end) { - error(Diagnostics.Undetermined_character_escape, pos - 1, 1); - return "\\"; - } pos--; - return scanEscapeSequence(/*shouldEmitInvalidEscapeError*/ unicodeMode, /*isRegularExpression*/ annexB ? "annex-b" : true); + return scanEscapeSequence(/*shouldEmitInvalidEscapeError*/ anyUnicodeMode, /*isRegularExpression*/ annexB ? "annex-b" : true); } } @@ -3433,11 +3463,11 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } } scanExpectedChar(CharacterCodes.closeBrace); - if (!unicodeMode) { + if (!anyUnicodeMode) { error(Diagnostics.Unicode_property_value_expressions_are_only_available_when_the_Unicode_u_flag_or_the_Unicode_Sets_v_flag_is_set, start, pos - start); } } - else if (unicodeMode) { + else if (anyUnicodeMode) { error(Diagnostics._0_must_be_followed_by_a_Unicode_property_value_expression_enclosed_in_braces, pos - 2, 2, String.fromCharCode(ch)); } return true; @@ -3459,7 +3489,7 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } function scanSourceCharacter(): string { - const size = unicodeMode ? charSize(charCodeChecked(pos)) : 1; + const size = anyUnicodeMode ? charSize(charCodeChecked(pos)) : 1; pos += size; return size > 0 ? text.substring(pos - size, pos) : ""; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b3c58c5c72719..692b51b592864 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2765,17 +2765,17 @@ export interface RegularExpressionLiteral extends LiteralExpression { // dprint-ignore /** @internal */ export const enum RegularExpressionFlags { - None = 0, - HasIndices = 1 << 0, // d - Global = 1 << 1, // g - IgnoreCase = 1 << 2, // i - Multiline = 1 << 3, // m - DotAll = 1 << 4, // s - Unicode = 1 << 5, // u - UnicodeSets = 1 << 6, // v - Sticky = 1 << 7, // y - UnicodeMode = Unicode | UnicodeSets, - Modifiers = IgnoreCase | Multiline | DotAll, + None = 0, + HasIndices = 1 << 0, // d + Global = 1 << 1, // g + IgnoreCase = 1 << 2, // i + Multiline = 1 << 3, // m + DotAll = 1 << 4, // s + Unicode = 1 << 5, // u + UnicodeSets = 1 << 6, // v + Sticky = 1 << 7, // y + AnyUnicodeMode = Unicode | UnicodeSets, + Modifiers = IgnoreCase | Multiline | DotAll, } export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration { diff --git a/src/testRunner/tests.ts b/src/testRunner/tests.ts index 5ffbf1edce9f8..de6374c4f0cd4 100644 --- a/src/testRunner/tests.ts +++ b/src/testRunner/tests.ts @@ -47,6 +47,7 @@ export * from "./unittests/paths.js"; export * from "./unittests/printer.js"; export * from "./unittests/programApi.js"; export * from "./unittests/publicApi.js"; +export * from "./unittests/regExpScannerRecovery.js"; export * from "./unittests/reuseProgramStructure.js"; export * from "./unittests/semver.js"; export * from "./unittests/services/cancellableLanguageServiceOperations.js"; diff --git a/src/testRunner/unittests/incrementalParser.ts b/src/testRunner/unittests/incrementalParser.ts index f508f80db71f7..ae464630afc0a 100644 --- a/src/testRunner/unittests/incrementalParser.ts +++ b/src/testRunner/unittests/incrementalParser.ts @@ -160,7 +160,7 @@ describe("unittests:: incrementalParser::", () => { const oldText = ts.ScriptSnapshot.fromString(source); const newTextAndChange = withInsert(oldText, semicolonIndex, "/"); - compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0); + compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4); }); it("Regular expression 2", () => { diff --git a/src/testRunner/unittests/regExpScannerRecovery.ts b/src/testRunner/unittests/regExpScannerRecovery.ts new file mode 100644 index 0000000000000..a4e6dd02a7394 --- /dev/null +++ b/src/testRunner/unittests/regExpScannerRecovery.ts @@ -0,0 +1,81 @@ +import * as ts from "../_namespaces/ts.js"; + +describe("unittests:: regExpScannerRecovery", () => { + const testCases = [ + "/", + "/[]", + "/{}", + "/()", + "/foo", + "/foo[]", + "/foo{}", + "/foo()", + "/[]foo", + "/{}foo", + "/()foo", + "/{[]}", + "/([])", + "/[)}({]", + "/({[]})", + "/\\[", + "/\\{", + "/\\(", + "/[\\[]", + "/(\\[)", + "/{\\[}", + "/[\\(]", + "/(\\()", + "/{\\(}", + "/[\\{]", + "/(\\{)", + "/{\\{}", + "/\\{(\\[\\([{])", + "/\\]", + "/\\}", + "/\\)", + "/[\\]]", + "/(\\])", + "/{\\]}", + "/[\\)]", + "/(\\))", + "/{\\)}", + "/[\\}]", + "/(\\})", + "/{\\}}", + "/({[\\])]})", + ]; + const whiteSpaceSequences = [ + "", + " ", + "\t\f", + "\u3000\u2003", + ]; + for (const testCase of testCases) { + for (const whiteSpaces of whiteSpaceSequences) { + const testCaseWithWhiteSpaces = testCase + whiteSpaces; + const sources = [ + `const regex = ${testCaseWithWhiteSpaces};`, + `(${testCaseWithWhiteSpaces});`, + `([${testCaseWithWhiteSpaces}]);`, + `({prop: ${testCaseWithWhiteSpaces}});`, + `({prop: ([(${testCaseWithWhiteSpaces})])});`, + `({[(${testCaseWithWhiteSpaces}).source]: 42});`, + ]; + for (const source of sources) { + it("stops parsing unterminated regexes at correct position: " + JSON.stringify(source), () => { + const { parseDiagnostics } = ts.createLanguageServiceSourceFile( + /*fileName*/ "", + ts.ScriptSnapshot.fromString(source), + ts.ScriptTarget.Latest, + /*version*/ "0", + /*setNodeParents*/ false, + ); + const diagnostic = ts.find(parseDiagnostics, ({ code }) => code === ts.Diagnostics.Unterminated_regular_expression_literal.code); + assert(diagnostic, "There should be an 'Unterminated regular expression literal.' error"); + assert.equal(diagnostic.start, source.indexOf("/"), "Diagnostic should start at where the regex starts"); + assert.equal(diagnostic.length, testCase.length, "Diagnostic should end at where the regex ends"); + }); + } + } + } +}); diff --git a/tests/baselines/reference/parser645086_1.errors.txt b/tests/baselines/reference/parser645086_1.errors.txt index 19271f9ae00ae..32871f9fe4cdc 100644 --- a/tests/baselines/reference/parser645086_1.errors.txt +++ b/tests/baselines/reference/parser645086_1.errors.txt @@ -1,13 +1,10 @@ parser645086_1.ts(1,13): error TS1005: ',' expected. parser645086_1.ts(1,14): error TS1134: Variable declaration expected. -parser645086_1.ts(1,15): error TS1161: Unterminated regular expression literal. -==== parser645086_1.ts (3 errors) ==== +==== parser645086_1.ts (2 errors) ==== var v = /[]/]/ ~ !!! error TS1005: ',' expected. ~ -!!! error TS1134: Variable declaration expected. - -!!! error TS1161: Unterminated regular expression literal. \ No newline at end of file +!!! error TS1134: Variable declaration expected. \ No newline at end of file diff --git a/tests/baselines/reference/parser645086_2.errors.txt b/tests/baselines/reference/parser645086_2.errors.txt index dffb43065e05d..7d1f93ee1bb95 100644 --- a/tests/baselines/reference/parser645086_2.errors.txt +++ b/tests/baselines/reference/parser645086_2.errors.txt @@ -1,13 +1,10 @@ parser645086_2.ts(1,14): error TS1005: ',' expected. parser645086_2.ts(1,15): error TS1134: Variable declaration expected. -parser645086_2.ts(1,16): error TS1161: Unterminated regular expression literal. -==== parser645086_2.ts (3 errors) ==== +==== parser645086_2.ts (2 errors) ==== var v = /[^]/]/ ~ !!! error TS1005: ',' expected. ~ -!!! error TS1134: Variable declaration expected. - -!!! error TS1161: Unterminated regular expression literal. \ No newline at end of file +!!! error TS1134: Variable declaration expected. \ No newline at end of file diff --git a/tests/baselines/reference/parserMissingToken2.errors.txt b/tests/baselines/reference/parserMissingToken2.errors.txt index e1f7c4f8fb230..71aab05b9600e 100644 --- a/tests/baselines/reference/parserMissingToken2.errors.txt +++ b/tests/baselines/reference/parserMissingToken2.errors.txt @@ -1,7 +1,7 @@ -parserMissingToken2.ts(1,2): error TS1161: Unterminated regular expression literal. +parserMissingToken2.ts(1,1): error TS1161: Unterminated regular expression literal. ==== parserMissingToken2.ts (1 errors) ==== / b; - + ~~~ !!! error TS1161: Unterminated regular expression literal. \ No newline at end of file diff --git a/tests/baselines/reference/parserMissingToken2.js b/tests/baselines/reference/parserMissingToken2.js index c4c6a4220d0ba..1faf9ad4971f9 100644 --- a/tests/baselines/reference/parserMissingToken2.js +++ b/tests/baselines/reference/parserMissingToken2.js @@ -4,4 +4,4 @@ / b; //// [parserMissingToken2.js] -/ b;; +/ b; diff --git a/tests/baselines/reference/parserMissingToken2.types b/tests/baselines/reference/parserMissingToken2.types index 88a03e78264e0..ef9428be503a2 100644 --- a/tests/baselines/reference/parserMissingToken2.types +++ b/tests/baselines/reference/parserMissingToken2.types @@ -2,6 +2,6 @@ === parserMissingToken2.ts === / b; ->/ b; : RegExp -> : ^^^^^^ +>/ b : RegExp +> : ^^^^^^ diff --git a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.errors.txt b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.errors.txt index 4b32921546956..afa9b3e5e4577 100644 --- a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.errors.txt +++ b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.errors.txt @@ -1,13 +1,10 @@ parserRegularExpressionDivideAmbiguity4.ts(1,1): error TS2304: Cannot find name 'foo'. -parserRegularExpressionDivideAmbiguity4.ts(1,6): error TS1161: Unterminated regular expression literal. -parserRegularExpressionDivideAmbiguity4.ts(1,17): error TS1005: ')' expected. +parserRegularExpressionDivideAmbiguity4.ts(1,5): error TS1161: Unterminated regular expression literal. -==== parserRegularExpressionDivideAmbiguity4.ts (3 errors) ==== +==== parserRegularExpressionDivideAmbiguity4.ts (2 errors) ==== foo(/notregexp); ~~~ !!! error TS2304: Cannot find name 'foo'. - -!!! error TS1161: Unterminated regular expression literal. - -!!! error TS1005: ')' expected. \ No newline at end of file + ~~~~~~~~~~ +!!! error TS1161: Unterminated regular expression literal. \ No newline at end of file diff --git a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.js b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.js index de84199b803ae..b8ec4550186b8 100644 --- a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.js +++ b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.js @@ -4,4 +4,4 @@ foo(/notregexp); //// [parserRegularExpressionDivideAmbiguity4.js] -foo(/notregexp);); +foo(/notregexp); diff --git a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.types b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.types index 0c2623d2d1269..49795f0955380 100644 --- a/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.types +++ b/tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.types @@ -2,10 +2,10 @@ === parserRegularExpressionDivideAmbiguity4.ts === foo(/notregexp); ->foo(/notregexp); : any -> : ^^^ +>foo(/notregexp) : any +> : ^^^ >foo : any > : ^^^ ->/notregexp); : RegExp -> : ^^^^^^ +>/notregexp : RegExp +> : ^^^^^^ diff --git a/tests/baselines/reference/tsxAttributeInvalidNames.errors.txt b/tests/baselines/reference/tsxAttributeInvalidNames.errors.txt index a4c162ac25d3d..339091ece5b38 100644 --- a/tests/baselines/reference/tsxAttributeInvalidNames.errors.txt +++ b/tests/baselines/reference/tsxAttributeInvalidNames.errors.txt @@ -10,7 +10,7 @@ file.tsx(11,1): error TS2362: The left-hand side of an arithmetic operation must file.tsx(11,8): error TS1003: Identifier expected. file.tsx(11,9): error TS2304: Cannot find name 'data'. file.tsx(11,13): error TS1005: ';' expected. -file.tsx(11,20): error TS1161: Unterminated regular expression literal. +file.tsx(11,19): error TS1161: Unterminated regular expression literal. ==== file.tsx (13 errors) ==== @@ -49,5 +49,5 @@ file.tsx(11,20): error TS1161: Unterminated regular expression literal. !!! error TS2304: Cannot find name 'data'. ~ !!! error TS1005: ';' expected. - + ~~ !!! error TS1161: Unterminated regular expression literal. \ No newline at end of file diff --git a/tests/baselines/reference/tsxAttributeInvalidNames.js b/tests/baselines/reference/tsxAttributeInvalidNames.js index 3e23b1986fec7..abaf80c3c462d 100644 --- a/tests/baselines/reference/tsxAttributeInvalidNames.js +++ b/tests/baselines/reference/tsxAttributeInvalidNames.js @@ -22,4 +22,4 @@ data = { 32: } / > ; { 32; } -/>;; +/>; diff --git a/tests/baselines/reference/tsxAttributeInvalidNames.types b/tests/baselines/reference/tsxAttributeInvalidNames.types index a90b7267d3b12..56531b6f75662 100644 --- a/tests/baselines/reference/tsxAttributeInvalidNames.types +++ b/tests/baselines/reference/tsxAttributeInvalidNames.types @@ -56,6 +56,6 @@ declare module JSX { > : ^^^ >32 : 32 > : ^^ ->/>; : RegExp -> : ^^^^^^ +>/> : RegExp +> : ^^^^^^ diff --git a/tests/baselines/reference/unterminatedRegexAtEndOfSource1.errors.txt b/tests/baselines/reference/unterminatedRegexAtEndOfSource1.errors.txt index 9050b11866bfb..0e291c50928c5 100644 --- a/tests/baselines/reference/unterminatedRegexAtEndOfSource1.errors.txt +++ b/tests/baselines/reference/unterminatedRegexAtEndOfSource1.errors.txt @@ -1,7 +1,7 @@ -unterminatedRegexAtEndOfSource1.ts(1,10): error TS1161: Unterminated regular expression literal. +unterminatedRegexAtEndOfSource1.ts(1,9): error TS1161: Unterminated regular expression literal. ==== unterminatedRegexAtEndOfSource1.ts (1 errors) ==== var a = / - + ~ !!! error TS1161: Unterminated regular expression literal. \ No newline at end of file diff --git a/tests/cases/fourslash/whiteSpaceTrimming4.ts b/tests/cases/fourslash/whiteSpaceTrimming4.ts index 59223197681ce..6989dfb907e3e 100644 --- a/tests/cases/fourslash/whiteSpaceTrimming4.ts +++ b/tests/cases/fourslash/whiteSpaceTrimming4.ts @@ -5,4 +5,4 @@ goTo.marker('1'); edit.insert("\n"); -verify.currentFileContentIs("var re = /\\w+ \n /;"); +verify.currentFileContentIs("var re = /\\w+\n /;");