From 1a5228d20452db775930201826fe8b46409a1097 Mon Sep 17 00:00:00 2001 From: graphemecluster Date: Sat, 2 Sep 2023 03:09:59 +0800 Subject: [PATCH] Provide Spelling Suggestions for Unicode Property Value Expressions --- src/compiler/core.ts | 2 +- src/compiler/parser.ts | 10 ++++++++-- src/compiler/scanner.ts | 13 ++++++++++++ ...pertyValueExpressionSuggestions.errors.txt | 20 +++++++++++++++++++ ...icodePropertyValueExpressionSuggestions.js | 8 ++++++++ ...PropertyValueExpressionSuggestions.symbols | 6 ++++++ ...dePropertyValueExpressionSuggestions.types | 7 +++++++ ...icodePropertyValueExpressionSuggestions.ts | 1 + 8 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.errors.txt create mode 100644 tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.js create mode 100644 tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.symbols create mode 100644 tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.types create mode 100644 tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 21eae040ba922..c65ed24656aee 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2322,7 +2322,7 @@ export function compareBooleans(a: boolean, b: boolean): Comparison { * * @internal */ -export function getSpellingSuggestion(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined { +export function getSpellingSuggestion(name: string, candidates: Iterable, 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; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 6b900302c96df..d696191fe1864 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -64,6 +64,7 @@ import { DeleteExpression, Diagnostic, DiagnosticArguments, + DiagnosticCategory, DiagnosticMessage, Diagnostics, DiagnosticWithDetachedLocation, @@ -115,6 +116,7 @@ import { HasModifiers, HeritageClause, Identifier, + identity, idText, IfStatement, ImportClause, @@ -2114,7 +2116,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) { + 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); } @@ -2370,7 +2376,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); if (suggestion) { parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); return; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 35d50a89161f7..c83c78909d090 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -12,6 +12,7 @@ import { DiagnosticMessage, Diagnostics, forEach, + getSpellingSuggestion, identity, JSDocSyntaxKind, JsxTokenSyntaxKind, @@ -3302,6 +3303,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } else if (propertyName === undefined) { error(Diagnostics.Unknown_Unicode_property_name, propertyNameOrValueStart, pos - propertyNameOrValueStart); + const suggestion = getSpellingSuggestion(propertyNameOrValue, nonBinaryUnicodeProperties.keys(), identity); + if (suggestion) { + error(Diagnostics.Did_you_mean_0, propertyNameOrValueStart, pos - propertyNameOrValueStart, suggestion); + } } pos++; const propertyValueStart = pos; @@ -3311,6 +3316,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } else if (propertyName !== undefined && !valuesOfNonBinaryUnicodeProperties[propertyName].has(propertyValue)) { error(Diagnostics.Unknown_Unicode_property_value, propertyValueStart, pos - propertyValueStart); + const suggestion = getSpellingSuggestion(propertyValue, valuesOfNonBinaryUnicodeProperties[propertyName], identity); + if (suggestion) { + error(Diagnostics.Did_you_mean_0, propertyValueStart, pos - propertyValueStart, suggestion); + } } } else { @@ -3330,6 +3339,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean } else if (!valuesOfNonBinaryUnicodeProperties.General_Category.has(propertyNameOrValue) && !binaryUnicodeProperties.has(propertyNameOrValue)) { error(Diagnostics.Unknown_Unicode_property_name_or_value, propertyNameOrValueStart, pos - propertyNameOrValueStart); + const suggestion = getSpellingSuggestion(propertyNameOrValue, [...valuesOfNonBinaryUnicodeProperties.General_Category, ...binaryUnicodeProperties, ...binaryUnicodePropertiesOfStrings], identity); + if (suggestion) { + error(Diagnostics.Did_you_mean_0, propertyNameOrValueStart, pos - propertyNameOrValueStart, suggestion); + } } } scanExpectedChar(CharacterCodes.closeBrace); diff --git a/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.errors.txt b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.errors.txt new file mode 100644 index 0000000000000..3e7b6909e83db --- /dev/null +++ b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.errors.txt @@ -0,0 +1,20 @@ +regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,19): error TS1527: Unknown Unicode property name or value. +regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,28): error TS1522: Unknown Unicode property name. +regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,45): error TS1524: Unknown Unicode property value. +regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,55): error TS1499: This regular expression flag is only available when targeting 'ES2015' or later. + + +==== regularExpressionUnicodePropertyValueExpressionSuggestions.ts (4 errors) ==== + const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u; + ~~~~~ +!!! error TS1527: Unknown Unicode property name or value. +!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:19: Did you mean 'ASCII'? + ~~ +!!! error TS1522: Unknown Unicode property name. +!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:28: Did you mean 'sc'? + ~~~~~~~~ +!!! error TS1524: Unknown Unicode property value. +!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:45: Did you mean 'Unknown'? + ~ +!!! error TS1499: This regular expression flag is only available when targeting 'ES2015' or later. + \ No newline at end of file diff --git a/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.js b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.js new file mode 100644 index 0000000000000..3e134fc01b56e --- /dev/null +++ b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.js @@ -0,0 +1,8 @@ +//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] //// + +//// [regularExpressionUnicodePropertyValueExpressionSuggestions.ts] +const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u; + + +//// [regularExpressionUnicodePropertyValueExpressionSuggestions.js] +var regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u; diff --git a/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.symbols b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.symbols new file mode 100644 index 0000000000000..c2d1297689992 --- /dev/null +++ b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.symbols @@ -0,0 +1,6 @@ +//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] //// + +=== regularExpressionUnicodePropertyValueExpressionSuggestions.ts === +const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u; +>regex : Symbol(regex, Decl(regularExpressionUnicodePropertyValueExpressionSuggestions.ts, 0, 5)) + diff --git a/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.types b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.types new file mode 100644 index 0000000000000..bf79211722c5c --- /dev/null +++ b/tests/baselines/reference/regularExpressionUnicodePropertyValueExpressionSuggestions.types @@ -0,0 +1,7 @@ +//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] //// + +=== regularExpressionUnicodePropertyValueExpressionSuggestions.ts === +const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u; +>regex : RegExp +>/\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u : RegExp + diff --git a/tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts b/tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts new file mode 100644 index 0000000000000..4fe1bef352d38 --- /dev/null +++ b/tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts @@ -0,0 +1 @@ +const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;