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.

0 commit comments

Comments
 (0)