Skip to content

Commit 4f8aa52

Browse files
authored
feat(45679): support 'did you mean' diagnostics for string literal union (#45723)
* feat(45679): support 'did you mean' diagnostics for string literal union * Format suggested type with `typeToString` * Address feedback
1 parent 8523ac8 commit 4f8aa52

8 files changed

+107
-4
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17831,6 +17831,13 @@ namespace ts {
1783117831
message = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_types_of_the_target_s_properties;
1783217832
}
1783317833
else {
17834+
if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) {
17835+
const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType);
17836+
if (suggestedType) {
17837+
reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType));
17838+
return;
17839+
}
17840+
}
1783417841
message = Diagnostics.Type_0_is_not_assignable_to_type_1;
1783517842
}
1783617843
}
@@ -28230,6 +28237,11 @@ namespace ts {
2823028237
return suggestion;
2823128238
}
2823228239

28240+
function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined {
28241+
const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral));
28242+
return getSpellingSuggestion(source.value, candidates, type => type.value);
28243+
}
28244+
2823328245
/**
2823428246
* Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
2823528247
* Names less than length 3 only check for case-insensitive equality, not levenshtein distance.

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,6 +3300,10 @@
33003300
"category": "Error",
33013301
"code": 2819
33023302
},
3303+
"Type '{0}' is not assignable to type '{1}'. Did you mean '{2}'?": {
3304+
"category": "Error",
3305+
"code": 2820
3306+
},
33033307

33043308
"Import declaration '{0}' is using private name '{1}'.": {
33053309
"category": "Error",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
tests/cases/compiler/didYouMeanStringLiteral.ts(5,7): error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'?
2+
tests/cases/compiler/didYouMeanStringLiteral.ts(6,7): error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'.
3+
tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'?
4+
5+
6+
==== tests/cases/compiler/didYouMeanStringLiteral.ts (3 errors) ====
7+
type T1 = "string" | "number" | "boolean";
8+
type T2 = T1 & ("number" | "boolean"); // "number" | "boolean"
9+
type T3 = T1 & ("string" | "boolean"); // "string" | "boolean"
10+
11+
const t1: T1 = "strong";
12+
~~
13+
!!! error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'?
14+
const t2: T2 = "strong";
15+
~~
16+
!!! error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'.
17+
const t3: T3 = "strong";
18+
~~
19+
!!! error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'?
20+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [didYouMeanStringLiteral.ts]
2+
type T1 = "string" | "number" | "boolean";
3+
type T2 = T1 & ("number" | "boolean"); // "number" | "boolean"
4+
type T3 = T1 & ("string" | "boolean"); // "string" | "boolean"
5+
6+
const t1: T1 = "strong";
7+
const t2: T2 = "strong";
8+
const t3: T3 = "strong";
9+
10+
11+
//// [didYouMeanStringLiteral.js]
12+
var t1 = "strong";
13+
var t2 = "strong";
14+
var t3 = "strong";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/didYouMeanStringLiteral.ts ===
2+
type T1 = "string" | "number" | "boolean";
3+
>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0))
4+
5+
type T2 = T1 & ("number" | "boolean"); // "number" | "boolean"
6+
>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42))
7+
>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0))
8+
9+
type T3 = T1 & ("string" | "boolean"); // "string" | "boolean"
10+
>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38))
11+
>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0))
12+
13+
const t1: T1 = "strong";
14+
>t1 : Symbol(t1, Decl(didYouMeanStringLiteral.ts, 4, 5))
15+
>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0))
16+
17+
const t2: T2 = "strong";
18+
>t2 : Symbol(t2, Decl(didYouMeanStringLiteral.ts, 5, 5))
19+
>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42))
20+
21+
const t3: T3 = "strong";
22+
>t3 : Symbol(t3, Decl(didYouMeanStringLiteral.ts, 6, 5))
23+
>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38))
24+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/didYouMeanStringLiteral.ts ===
2+
type T1 = "string" | "number" | "boolean";
3+
>T1 : T1
4+
5+
type T2 = T1 & ("number" | "boolean"); // "number" | "boolean"
6+
>T2 : "number" | "boolean"
7+
8+
type T3 = T1 & ("string" | "boolean"); // "string" | "boolean"
9+
>T3 : "string" | "boolean"
10+
11+
const t1: T1 = "strong";
12+
>t1 : T1
13+
>"strong" : "strong"
14+
15+
const t2: T2 = "strong";
16+
>t2 : "number" | "boolean"
17+
>"strong" : "strong"
18+
19+
const t3: T3 = "strong";
20+
>t3 : "string" | "boolean"
21+
>"strong" : "strong"
22+

tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'.
2-
tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'.
1+
tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'?
2+
tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'?
33

44

55
==== tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts (2 errors) ====
@@ -15,15 +15,15 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS232
1515
{ kind: "bluray", },
1616
{ kind: "hdpvd", }
1717
~~~~
18-
!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'.
18+
!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'?
1919
!!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc'
2020
]);
2121

2222
const ds: Disc[] = [
2323
{ kind: "bluray", },
2424
{ kind: "hdpvd", }
2525
~~~~
26-
!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'.
26+
!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'?
2727
!!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc'
2828
];
2929
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type T1 = "string" | "number" | "boolean";
2+
type T2 = T1 & ("number" | "boolean"); // "number" | "boolean"
3+
type T3 = T1 & ("string" | "boolean"); // "string" | "boolean"
4+
5+
const t1: T1 = "strong";
6+
const t2: T2 = "strong";
7+
const t3: T3 = "strong";

0 commit comments

Comments
 (0)