Skip to content

Commit 416f31f

Browse files
Provide Spelling Suggestions for Unicode Property Value Expressions
1 parent 7bac4af commit 416f31f

11 files changed

+87
-22
lines changed

src/compiler/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2322,7 +2322,7 @@ export function compareBooleans(a: boolean, b: boolean): Comparison {
23222322
*
23232323
* @internal
23242324
*/
2325-
export function getSpellingSuggestion<T>(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined {
2325+
export function getSpellingSuggestion<T>(name: string, candidates: Iterable<T>, getName: (candidate: T) => string | undefined): T | undefined {
23262326
const maximumLengthDifference = Math.max(2, Math.floor(name.length * 0.34));
23272327
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother.
23282328
let bestCandidate: T | undefined;

src/compiler/parser.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
DeleteExpression,
6565
Diagnostic,
6666
DiagnosticArguments,
67+
DiagnosticCategory,
6768
DiagnosticMessage,
6869
Diagnostics,
6970
DiagnosticWithDetachedLocation,
@@ -115,6 +116,7 @@ import {
115116
HasModifiers,
116117
HeritageClause,
117118
Identifier,
119+
identity,
118120
idText,
119121
IfStatement,
120122
ImportClause,
@@ -2114,7 +2116,11 @@ namespace Parser {
21142116
// Don't report another error if it would just be at the same position as the last error.
21152117
const lastError = lastOrUndefined(parseDiagnostics);
21162118
let result: DiagnosticWithDetachedLocation | undefined;
2117-
if (!lastError || start !== lastError.start) {
2119+
if (message.category === DiagnosticCategory.Message && lastError && start === lastError.start && length === lastError.length) {
2120+
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
2121+
addRelatedInfo(lastError, result);
2122+
}
2123+
else if (!lastError || start !== lastError.start) {
21182124
result = createDetachedDiagnostic(fileName, sourceText, start, length, message, ...args);
21192125
parseDiagnostics.push(result);
21202126
}
@@ -2129,12 +2135,12 @@ namespace Parser {
21292135
return parseErrorAtPosition(start, end - start, message, ...args);
21302136
}
21312137

2132-
function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, ...args: DiagnosticArguments): void {
2133-
parseErrorAt(range.pos, range.end, message, ...args);
2138+
function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, ...args: DiagnosticArguments): DiagnosticWithDetachedLocation | undefined {
2139+
return parseErrorAt(range.pos, range.end, message, ...args);
21342140
}
21352141

2136-
function scanError(message: DiagnosticMessage, length: number, arg0?: any): void {
2137-
parseErrorAtPosition(scanner.getTokenEnd(), length, message, arg0);
2142+
function scanError(message: DiagnosticMessage, pos: number, length: number, ...args: DiagnosticArguments): DiagnosticWithDetachedLocation | undefined {
2143+
return parseErrorAtPosition(pos, length, message, ...args);
21382144
}
21392145

21402146
function getNodePos(): number {
@@ -2370,7 +2376,7 @@ namespace Parser {
23702376
}
23712377

23722378
// The user alternatively might have misspelled or forgotten to add a space after a common keyword.
2373-
const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText);
2379+
const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, identity) ?? getSpaceSuggestion(expressionText);
23742380
if (suggestion) {
23752381
parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion);
23762382
return;

src/compiler/scanner.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import {
99
CommentRange,
1010
compareValues,
1111
Debug,
12+
DiagnosticArguments,
1213
DiagnosticMessage,
1314
Diagnostics,
15+
DiagnosticWithDetachedLocation,
1416
forEach,
17+
getSpellingSuggestion,
1518
identity,
1619
JSDocSyntaxKind,
1720
JsxTokenSyntaxKind,
@@ -31,7 +34,7 @@ import {
3134
TokenFlags,
3235
} from "./_namespaces/ts";
3336

34-
export type ErrorCallback = (message: DiagnosticMessage, length: number, arg0?: any) => void;
37+
export type ErrorCallback = (message: DiagnosticMessage, pos: number, length: number, ...args: DiagnosticArguments) => DiagnosticWithDetachedLocation | undefined;
3538

3639
/** @internal */
3740
export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean {
@@ -1119,15 +1122,8 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
11191122

11201123
return scanner;
11211124

1122-
function error(message: DiagnosticMessage): void;
1123-
function error(message: DiagnosticMessage, errPos: number, length: number, arg0?: any): void;
1124-
function error(message: DiagnosticMessage, errPos: number = pos, length?: number, arg0?: any): void {
1125-
if (onError) {
1126-
const oldPos = pos;
1127-
pos = errPos;
1128-
onError(message, length || 0, arg0);
1129-
pos = oldPos;
1130-
}
1125+
function error(message: DiagnosticMessage, errPos = pos, length = 0, ...args: DiagnosticArguments): DiagnosticWithDetachedLocation | undefined {
1126+
return onError?.(message, errPos, length, ...args);
11311127
}
11321128

11331129
function scanNumberFragment(): string {
@@ -3290,6 +3286,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
32903286
}
32913287
else if (propertyName === undefined) {
32923288
error(Diagnostics.Unknown_Unicode_property_name, propertyNameOrValueStart, pos - propertyNameOrValueStart);
3289+
const suggestion = getSpellingSuggestion(propertyNameOrValue, nonBinaryUnicodeProperties.keys(), identity);
3290+
if (suggestion) {
3291+
error(Diagnostics.Did_you_mean_0, propertyNameOrValueStart, pos - propertyNameOrValueStart, suggestion);
3292+
}
32933293
}
32943294
pos++;
32953295
const propertyValueStart = pos;
@@ -3299,6 +3299,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
32993299
}
33003300
else if (propertyName !== undefined && !valuesOfNonBinaryUnicodeProperties[propertyName].has(propertyValue)) {
33013301
error(Diagnostics.Unknown_Unicode_property_value, propertyValueStart, pos - propertyValueStart);
3302+
const suggestion = getSpellingSuggestion(propertyValue, valuesOfNonBinaryUnicodeProperties[propertyName], identity);
3303+
if (suggestion) {
3304+
error(Diagnostics.Did_you_mean_0, propertyValueStart, pos - propertyValueStart, suggestion);
3305+
}
33023306
}
33033307
}
33043308
else {
@@ -3318,6 +3322,10 @@ export function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean
33183322
}
33193323
else if (!valuesOfNonBinaryUnicodeProperties.General_Category.has(propertyNameOrValue) && !binaryUnicodeProperties.has(propertyNameOrValue)) {
33203324
error(Diagnostics.Unknown_Unicode_property_name_or_value, propertyNameOrValueStart, pos - propertyNameOrValueStart);
3325+
const suggestion = getSpellingSuggestion(propertyNameOrValue, [...valuesOfNonBinaryUnicodeProperties.General_Category, ...binaryUnicodeProperties, ...binaryUnicodePropertiesOfStrings], identity);
3326+
if (suggestion) {
3327+
error(Diagnostics.Did_you_mean_0, propertyNameOrValueStart, pos - propertyNameOrValueStart, suggestion);
3328+
}
33213329
}
33223330
}
33233331
scanExpectedChar(CharacterCodes.closeBrace);

src/compiler/types.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6924,10 +6924,8 @@ export interface Diagnostic extends DiagnosticRelatedInformation {
69246924
/** @internal */ skippedOn?: keyof CompilerOptions;
69256925
}
69266926

6927-
/** @internal */
69286927
export type DiagnosticArguments = (string | number)[];
69296928

6930-
/** @internal */
69316929
export type DiagnosticAndArguments = [message: DiagnosticMessage, ...args: DiagnosticArguments];
69326930

69336931
export interface DiagnosticRelatedInformation {
@@ -6945,7 +6943,6 @@ export interface DiagnosticWithLocation extends Diagnostic {
69456943
length: number;
69466944
}
69476945

6948-
/** @internal */
69496946
export interface DiagnosticWithDetachedLocation extends Diagnostic {
69506947
file: undefined;
69516948
fileName: string;
@@ -6959,6 +6956,7 @@ export enum DiagnosticCategory {
69596956
Suggestion,
69606957
Message,
69616958
}
6959+
69626960
/** @internal */
69636961
export function diagnosticCategoryName(d: { category: DiagnosticCategory; }, lowerCase = true): string {
69646962
const name = DiagnosticCategory[d.category];

src/compiler/utilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9788,7 +9788,7 @@ export function isValidBigIntString(s: string, roundTripOnly: boolean): boolean
97889788
if (s === "") return false;
97899789
const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false);
97909790
let success = true;
9791-
scanner.setOnError(() => success = false);
9791+
scanner.setOnError(() => void (success = false));
97929792
scanner.setText(s + "n");
97939793
let result = scanner.scan();
97949794
const negative = result === SyntaxKind.MinusToken;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7335,6 +7335,11 @@ declare namespace ts {
73357335
source?: string;
73367336
relatedInformation?: DiagnosticRelatedInformation[];
73377337
}
7338+
type DiagnosticArguments = (string | number)[];
7339+
type DiagnosticAndArguments = [
7340+
message: DiagnosticMessage,
7341+
...args: DiagnosticArguments,
7342+
];
73387343
interface DiagnosticRelatedInformation {
73397344
category: DiagnosticCategory;
73407345
code: number;
@@ -7348,6 +7353,12 @@ declare namespace ts {
73487353
start: number;
73497354
length: number;
73507355
}
7356+
interface DiagnosticWithDetachedLocation extends Diagnostic {
7357+
file: undefined;
7358+
fileName: string;
7359+
start: number;
7360+
length: number;
7361+
}
73517362
enum DiagnosticCategory {
73527363
Warning = 0,
73537364
Error = 1,
@@ -8736,7 +8747,7 @@ declare namespace ts {
87368747
function isIdentifierStart(ch: number, languageVersion: ScriptTarget | undefined): boolean;
87378748
function isIdentifierPart(ch: number, languageVersion: ScriptTarget | undefined, identifierVariant?: LanguageVariant): boolean;
87388749
function createScanner(languageVersion: ScriptTarget, skipTrivia: boolean, languageVariant?: LanguageVariant, textInitial?: string, onError?: ErrorCallback, start?: number, length?: number): Scanner;
8739-
type ErrorCallback = (message: DiagnosticMessage, length: number, arg0?: any) => void;
8750+
type ErrorCallback = (message: DiagnosticMessage, pos: number, length: number, ...args: DiagnosticArguments) => DiagnosticWithDetachedLocation | undefined;
87408751
interface Scanner {
87418752
/** @deprecated use {@link getTokenFullStart} */
87428753
getStartPos(): number;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,19): error TS1527: Unknown Unicode property name or value.
2+
regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,28): error TS1522: Unknown Unicode property name.
3+
regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,45): error TS1524: Unknown Unicode property value.
4+
regularExpressionUnicodePropertyValueExpressionSuggestions.ts(1,55): error TS1499: This regular expression flag is only available when targeting 'ES2015' or later.
5+
6+
7+
==== regularExpressionUnicodePropertyValueExpressionSuggestions.ts (4 errors) ====
8+
const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;
9+
~~~~~
10+
!!! error TS1527: Unknown Unicode property name or value.
11+
!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:19: Did you mean 'ASCII'?
12+
~~
13+
!!! error TS1522: Unknown Unicode property name.
14+
!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:28: Did you mean 'sc'?
15+
~~~~~~~~
16+
!!! error TS1524: Unknown Unicode property value.
17+
!!! related TS1369 regularExpressionUnicodePropertyValueExpressionSuggestions.ts:1:45: Did you mean 'Unknown'?
18+
~
19+
!!! error TS1499: This regular expression flag is only available when targeting 'ES2015' or later.
20+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] ////
2+
3+
//// [regularExpressionUnicodePropertyValueExpressionSuggestions.ts]
4+
const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;
5+
6+
7+
//// [regularExpressionUnicodePropertyValueExpressionSuggestions.js]
8+
var regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] ////
2+
3+
=== regularExpressionUnicodePropertyValueExpressionSuggestions.ts ===
4+
const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;
5+
>regex : Symbol(regex, Decl(regularExpressionUnicodePropertyValueExpressionSuggestions.ts, 0, 5))
6+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//// [tests/cases/compiler/regularExpressionUnicodePropertyValueExpressionSuggestions.ts] ////
2+
3+
=== regularExpressionUnicodePropertyValueExpressionSuggestions.ts ===
4+
const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;
5+
>regex : RegExp
6+
>/\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u : RegExp
7+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const regex = /\p{ascii}\p{Sc=Unknown}\p{sc=unknownX}/u;

0 commit comments

Comments
 (0)