Skip to content

Commit d0ef028

Browse files
Improve Recovery of Unterminated Regular Expressions (#58289)
Co-authored-by: Ron Buckton <[email protected]>
1 parent 842cf17 commit d0ef028

20 files changed

+223
-125
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31974,7 +31974,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3197431974
scanner.setScriptTarget(sourceFile.languageVersion);
3197531975
scanner.setLanguageVariant(sourceFile.languageVariant);
3197631976
scanner.setOnError((message, length, arg0) => {
31977-
// emulate `parseErrorAtPosition` from parser.ts
31977+
// For providing spelling suggestions
3197831978
const start = scanner!.getTokenEnd();
3197931979
if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) {
3198031980
const error = createDetachedDiagnostic(sourceFile.fileName, sourceFile.text, start, length, message, arg0);

src/compiler/parser.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ import {
6262
DeleteExpression,
6363
Diagnostic,
6464
DiagnosticArguments,
65-
DiagnosticCategory,
6665
DiagnosticMessage,
6766
Diagnostics,
6867
DiagnosticWithDetachedLocation,
@@ -2144,11 +2143,7 @@ namespace Parser {
21442143
// Don't report another error if it would just be at the same position as the last error.
21452144
const lastError = lastOrUndefined(parseDiagnostics);
21462145
let result: DiagnosticWithDetachedLocation | undefined;
2147-
if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) {
2148-
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
2149-
addRelatedInfo(lastError, result);
2150-
}
2151-
else if (!lastError || start !== lastError.start) {
2146+
if (!lastError || start !== lastError.start) {
21522147
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
21532148
parseDiagnostics.push(result);
21542149
}

src/compiler/scanner.ts

Lines changed: 101 additions & 71 deletions
Large diffs are not rendered by default.

src/compiler/types.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,17 +2765,17 @@ export interface RegularExpressionLiteral extends LiteralExpression {
27652765
// dprint-ignore
27662766
/** @internal */
27672767
export const enum RegularExpressionFlags {
2768-
None = 0,
2769-
HasIndices = 1 << 0, // d
2770-
Global = 1 << 1, // g
2771-
IgnoreCase = 1 << 2, // i
2772-
Multiline = 1 << 3, // m
2773-
DotAll = 1 << 4, // s
2774-
Unicode = 1 << 5, // u
2775-
UnicodeSets = 1 << 6, // v
2776-
Sticky = 1 << 7, // y
2777-
UnicodeMode = Unicode | UnicodeSets,
2778-
Modifiers = IgnoreCase | Multiline | DotAll,
2768+
None = 0,
2769+
HasIndices = 1 << 0, // d
2770+
Global = 1 << 1, // g
2771+
IgnoreCase = 1 << 2, // i
2772+
Multiline = 1 << 3, // m
2773+
DotAll = 1 << 4, // s
2774+
Unicode = 1 << 5, // u
2775+
UnicodeSets = 1 << 6, // v
2776+
Sticky = 1 << 7, // y
2777+
AnyUnicodeMode = Unicode | UnicodeSets,
2778+
Modifiers = IgnoreCase | Multiline | DotAll,
27792779
}
27802780

27812781
export interface NoSubstitutionTemplateLiteral extends LiteralExpression, TemplateLiteralLikeNode, Declaration {

src/testRunner/tests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export * from "./unittests/paths.js";
4747
export * from "./unittests/printer.js";
4848
export * from "./unittests/programApi.js";
4949
export * from "./unittests/publicApi.js";
50+
export * from "./unittests/regExpScannerRecovery.js";
5051
export * from "./unittests/reuseProgramStructure.js";
5152
export * from "./unittests/semver.js";
5253
export * from "./unittests/services/cancellableLanguageServiceOperations.js";

src/testRunner/unittests/incrementalParser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe("unittests:: incrementalParser::", () => {
160160
const oldText = ts.ScriptSnapshot.fromString(source);
161161
const newTextAndChange = withInsert(oldText, semicolonIndex, "/");
162162

163-
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
163+
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
164164
});
165165

166166
it("Regular expression 2", () => {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as ts from "../_namespaces/ts.js";
2+
3+
describe("unittests:: regExpScannerRecovery", () => {
4+
const testCases = [
5+
"/",
6+
"/[]",
7+
"/{}",
8+
"/()",
9+
"/foo",
10+
"/foo[]",
11+
"/foo{}",
12+
"/foo()",
13+
"/[]foo",
14+
"/{}foo",
15+
"/()foo",
16+
"/{[]}",
17+
"/([])",
18+
"/[)}({]",
19+
"/({[]})",
20+
"/\\[",
21+
"/\\{",
22+
"/\\(",
23+
"/[\\[]",
24+
"/(\\[)",
25+
"/{\\[}",
26+
"/[\\(]",
27+
"/(\\()",
28+
"/{\\(}",
29+
"/[\\{]",
30+
"/(\\{)",
31+
"/{\\{}",
32+
"/\\{(\\[\\([{])",
33+
"/\\]",
34+
"/\\}",
35+
"/\\)",
36+
"/[\\]]",
37+
"/(\\])",
38+
"/{\\]}",
39+
"/[\\)]",
40+
"/(\\))",
41+
"/{\\)}",
42+
"/[\\}]",
43+
"/(\\})",
44+
"/{\\}}",
45+
"/({[\\])]})",
46+
];
47+
const whiteSpaceSequences = [
48+
"",
49+
" ",
50+
"\t\f",
51+
"\u3000\u2003",
52+
];
53+
for (const testCase of testCases) {
54+
for (const whiteSpaces of whiteSpaceSequences) {
55+
const testCaseWithWhiteSpaces = testCase + whiteSpaces;
56+
const sources = [
57+
`const regex = ${testCaseWithWhiteSpaces};`,
58+
`(${testCaseWithWhiteSpaces});`,
59+
`([${testCaseWithWhiteSpaces}]);`,
60+
`({prop: ${testCaseWithWhiteSpaces}});`,
61+
`({prop: ([(${testCaseWithWhiteSpaces})])});`,
62+
`({[(${testCaseWithWhiteSpaces}).source]: 42});`,
63+
];
64+
for (const source of sources) {
65+
it("stops parsing unterminated regexes at correct position: " + JSON.stringify(source), () => {
66+
const { parseDiagnostics } = ts.createLanguageServiceSourceFile(
67+
/*fileName*/ "",
68+
ts.ScriptSnapshot.fromString(source),
69+
ts.ScriptTarget.Latest,
70+
/*version*/ "0",
71+
/*setNodeParents*/ false,
72+
);
73+
const diagnostic = ts.find(parseDiagnostics, ({ code }) => code === ts.Diagnostics.Unterminated_regular_expression_literal.code);
74+
assert(diagnostic, "There should be an 'Unterminated regular expression literal.' error");
75+
assert.equal(diagnostic.start, source.indexOf("/"), "Diagnostic should start at where the regex starts");
76+
assert.equal(diagnostic.length, testCase.length, "Diagnostic should end at where the regex ends");
77+
});
78+
}
79+
}
80+
}
81+
});
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
parser645086_1.ts(1,13): error TS1005: ',' expected.
22
parser645086_1.ts(1,14): error TS1134: Variable declaration expected.
3-
parser645086_1.ts(1,15): error TS1161: Unterminated regular expression literal.
43

54

6-
==== parser645086_1.ts (3 errors) ====
5+
==== parser645086_1.ts (2 errors) ====
76
var v = /[]/]/
87
~
98
!!! error TS1005: ',' expected.
109
~
11-
!!! error TS1134: Variable declaration expected.
12-
13-
!!! error TS1161: Unterminated regular expression literal.
10+
!!! error TS1134: Variable declaration expected.
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
parser645086_2.ts(1,14): error TS1005: ',' expected.
22
parser645086_2.ts(1,15): error TS1134: Variable declaration expected.
3-
parser645086_2.ts(1,16): error TS1161: Unterminated regular expression literal.
43

54

6-
==== parser645086_2.ts (3 errors) ====
5+
==== parser645086_2.ts (2 errors) ====
76
var v = /[^]/]/
87
~
98
!!! error TS1005: ',' expected.
109
~
11-
!!! error TS1134: Variable declaration expected.
12-
13-
!!! error TS1161: Unterminated regular expression literal.
10+
!!! error TS1134: Variable declaration expected.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
parserMissingToken2.ts(1,2): error TS1161: Unterminated regular expression literal.
1+
parserMissingToken2.ts(1,1): error TS1161: Unterminated regular expression literal.
22

33

44
==== parserMissingToken2.ts (1 errors) ====
55
/ b;
6-
6+
~~~
77
!!! error TS1161: Unterminated regular expression literal.

tests/baselines/reference/parserMissingToken2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
/ b;
55
66
//// [parserMissingToken2.js]
7-
/ b;;
7+
/ b;

tests/baselines/reference/parserMissingToken2.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
=== parserMissingToken2.ts ===
44
/ b;
5-
>/ b; : RegExp
6-
> : ^^^^^^
5+
>/ b : RegExp
6+
> : ^^^^^^
77

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
parserRegularExpressionDivideAmbiguity4.ts(1,1): error TS2304: Cannot find name 'foo'.
2-
parserRegularExpressionDivideAmbiguity4.ts(1,6): error TS1161: Unterminated regular expression literal.
3-
parserRegularExpressionDivideAmbiguity4.ts(1,17): error TS1005: ')' expected.
2+
parserRegularExpressionDivideAmbiguity4.ts(1,5): error TS1161: Unterminated regular expression literal.
43

54

6-
==== parserRegularExpressionDivideAmbiguity4.ts (3 errors) ====
5+
==== parserRegularExpressionDivideAmbiguity4.ts (2 errors) ====
76
foo(/notregexp);
87
~~~
98
!!! error TS2304: Cannot find name 'foo'.
10-
11-
!!! error TS1161: Unterminated regular expression literal.
12-
13-
!!! error TS1005: ')' expected.
9+
~~~~~~~~~~
10+
!!! error TS1161: Unterminated regular expression literal.

tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
foo(/notregexp);
55
66
//// [parserRegularExpressionDivideAmbiguity4.js]
7-
foo(/notregexp););
7+
foo(/notregexp);

tests/baselines/reference/parserRegularExpressionDivideAmbiguity4.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
=== parserRegularExpressionDivideAmbiguity4.ts ===
44
foo(/notregexp);
5-
>foo(/notregexp); : any
6-
> : ^^^
5+
>foo(/notregexp) : any
6+
> : ^^^
77
>foo : any
88
> : ^^^
9-
>/notregexp); : RegExp
10-
> : ^^^^^^
9+
>/notregexp : RegExp
10+
> : ^^^^^^
1111

tests/baselines/reference/tsxAttributeInvalidNames.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ file.tsx(11,1): error TS2362: The left-hand side of an arithmetic operation must
1010
file.tsx(11,8): error TS1003: Identifier expected.
1111
file.tsx(11,9): error TS2304: Cannot find name 'data'.
1212
file.tsx(11,13): error TS1005: ';' expected.
13-
file.tsx(11,20): error TS1161: Unterminated regular expression literal.
13+
file.tsx(11,19): error TS1161: Unterminated regular expression literal.
1414

1515

1616
==== file.tsx (13 errors) ====
@@ -49,5 +49,5 @@ file.tsx(11,20): error TS1161: Unterminated regular expression literal.
4949
!!! error TS2304: Cannot find name 'data'.
5050
~
5151
!!! error TS1005: ';' expected.
52-
52+
~~
5353
!!! error TS1161: Unterminated regular expression literal.

tests/baselines/reference/tsxAttributeInvalidNames.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ data = { 32: } / > ;
2222
{
2323
32;
2424
}
25-
/>;;
25+
/>;

tests/baselines/reference/tsxAttributeInvalidNames.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ declare module JSX {
5656
> : ^^^
5757
>32 : 32
5858
> : ^^
59-
>/>; : RegExp
60-
> : ^^^^^^
59+
>/> : RegExp
60+
> : ^^^^^^
6161

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
unterminatedRegexAtEndOfSource1.ts(1,10): error TS1161: Unterminated regular expression literal.
1+
unterminatedRegexAtEndOfSource1.ts(1,9): error TS1161: Unterminated regular expression literal.
22

33

44
==== unterminatedRegexAtEndOfSource1.ts (1 errors) ====
55
var a = /
6-
6+
~
77
!!! error TS1161: Unterminated regular expression literal.

tests/cases/fourslash/whiteSpaceTrimming4.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
goTo.marker('1');
66
edit.insert("\n");
77

8-
verify.currentFileContentIs("var re = /\\w+ \n /;");
8+
verify.currentFileContentIs("var re = /\\w+\n /;");

0 commit comments

Comments
 (0)