Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide Syntax Checking for Regular Expressions #55600

Merged
merged 14 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2289,7 +2289,7 @@ export function compareBooleans(a: boolean, b: boolean): Comparison {
*
* @internal
*/
export function getSpellingSuggestion<T>(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined {
export function getSpellingSuggestion<T>(name: string, candidates: Iterable<T>, getName: (candidate: T) => string | undefined): T | undefined {
const maximumLengthDifference = Math.max(2, Math.floor(name.length * 0.34));
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother.
let bestCandidate: T | undefined;
Expand Down
148 changes: 148 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,154 @@
"category": "Error",
"code": 1498
},
"Unknown regular expression flag.": {
"category": "Error",
"code": 1499
},
"Duplicate regular expression flag.": {
"category": "Error",
"code": 1500
},
"This regular expression flag is only available when targeting '{0}' or later.": {
"category": "Error",
"code": 1501
},
"The Unicode (u) flag and the Unicode Sets (v) flag cannot be set simultaneously.": {
"category": "Error",
"code": 1502
},
"Named capturing groups are only available when targeting 'ES2018' or later.": {
"category": "Error",
"code": 1503
},
"Subpattern flags must be present when there is a minus sign.": {
"category": "Error",
"code": 1504
},
"Incomplete quantifier. Digit expected.": {
"category": "Error",
"code": 1505
},
"Numbers out of order in quantifier.": {
"category": "Error",
"code": 1506
},
"There is nothing available for repetition.": {
"category": "Error",
"code": 1507
},
"Unexpected '{0}'. Did you mean to escape it with backslash?": {
"category": "Error",
"code": 1508
},
"This regular expression flag cannot be toggled within a subpattern.": {
"category": "Error",
"code": 1509
},
"'\\k' must be followed by a capturing group name enclosed in angle brackets.": {
"category": "Error",
"code": 1510
},
"'\\q' is only available inside character class.": {
"category": "Error",
"code": 1511
},
"'\\c' must be followed by an ASCII letter.": {
"category": "Error",
"code": 1512
},
"Undetermined character escape.": {
"category": "Error",
"code": 1513
},
"Expected a capturing group name.": {
"category": "Error",
"code": 1514
},
"Named capturing groups with the same name must be mutually exclusive to each other.": {
"category": "Error",
"code": 1515
},
"A character class range must not be bounded by another character class.": {
"category": "Error",
"code": 1516
},
"Range out of order in character class.": {
"category": "Error",
"code": 1517
},
"Anything that would possibly match more than a single character is invalid inside a negated character class.": {
"category": "Error",
"code": 1518
},
"Operators must not be mixed within a character class. Wrap it in a nested class instead.": {
"category": "Error",
"code": 1519
},
"Expected a class set oprand.": {
"category": "Error",
"code": 1520
},
"'\\q' must be followed by string alternatives enclosed in braces.": {
"category": "Error",
"code": 1521
},
"A character class must not contain a reserved double punctuator. Did you mean to escape it with backslash?": {
"category": "Error",
"code": 1522
},
"Expected a Unicode property name.": {
"category": "Error",
"code": 1523
},
"Unknown Unicode property name.": {
"category": "Error",
"code": 1524
},
"Expected a Unicode property value.": {
"category": "Error",
"code": 1525
},
"Unknown Unicode property value.": {
"category": "Error",
"code": 1526
},
"Expected a Unicode property name or value.": {
"category": "Error",
"code": 1527
},
"Any Unicode property that would possibly match more than a single character is only available when the Unicode Sets (v) flag is set.": {
"category": "Error",
"code": 1528
},
"Unknown Unicode property name or value.": {
"category": "Error",
"code": 1529
},
"Unicode property value expressions are only available when the Unicode (u) flag or the Unicode Sets (v) flag is set.": {
"category": "Error",
"code": 1530
},
"'\\{0}' must be followed by a Unicode property value expression enclosed in braces.": {
"category": "Error",
"code": 1531
},
"There is no capturing group named '{0}' in this regular expression.": {
"category": "Error",
"code": 1532
},
"A decimal escape must refer to an existent capturing group. There are only {0} capturing groups in this regular expression.": {
"category": "Error",
"code": 1533
},
"Decimal escapes are invalid when there are no capturing groups in a regular expression.": {
"category": "Error",
"code": 1534
},
"This character cannot be escaped in a regular expression.": {
"category": "Error",
"code": 1535
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
DeleteExpression,
Diagnostic,
DiagnosticArguments,
DiagnosticCategory,
DiagnosticMessage,
Diagnostics,
DiagnosticWithDetachedLocation,
Expand Down Expand Up @@ -113,6 +114,7 @@ import {
HasModifiers,
HeritageClause,
Identifier,
identity,
idText,
IfStatement,
ImportAttribute,
Expand Down Expand Up @@ -2142,7 +2144,11 @@ 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 (!lastError || start !== lastError.start) {
if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) {
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
addRelatedInfo(lastError, result);
}
else if (!lastError || start !== lastError.start) {
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
parseDiagnostics.push(result);
}
Expand Down Expand Up @@ -2398,7 +2404,7 @@ namespace Parser {
}

// The user alternatively might have misspelled or forgotten to add a space after a common keyword.
const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText);
const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, identity) ?? getSpaceSuggestion(expressionText);
rbuckton marked this conversation as resolved.
Show resolved Hide resolved
if (suggestion) {
parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion);
return;
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import {
getLineStarts,
getMatchedFileSpec,
getMatchedIncludeSpec,
getNameOfScriptTarget,
getNewLineCharacter,
getNormalizedAbsolutePath,
getNormalizedAbsolutePathWithoutRoot,
Expand Down Expand Up @@ -307,7 +308,6 @@ import {
SyntaxKind,
sys,
System,
targetOptionDeclaration,
toFileNameLowerCase,
tokenToString,
toPath as ts_toPath,
Expand Down Expand Up @@ -4769,7 +4769,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
message = Diagnostics.File_is_library_specified_here;
break;
}
const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined);
const target = getNameOfScriptTarget(getEmitScriptTarget(options));
configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined;
message = Diagnostics.File_is_default_library_for_target_specified_here;
break;
Expand Down
Loading