From 4f8aa5239ea6b916b3b768f7d3757c3f288ab186 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 15 Sep 2021 00:53:36 +0900 Subject: [PATCH] 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 --- src/compiler/checker.ts | 12 ++++++++++ src/compiler/diagnosticMessages.json | 4 ++++ .../didYouMeanStringLiteral.errors.txt | 20 ++++++++++++++++ .../reference/didYouMeanStringLiteral.js | 14 +++++++++++ .../reference/didYouMeanStringLiteral.symbols | 24 +++++++++++++++++++ .../reference/didYouMeanStringLiteral.types | 22 +++++++++++++++++ ...sForCallAndAssignmentAreSimilar.errors.txt | 8 +++---- .../cases/compiler/didYouMeanStringLiteral.ts | 7 ++++++ 8 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.errors.txt create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.js create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.symbols create mode 100644 tests/baselines/reference/didYouMeanStringLiteral.types create mode 100644 tests/cases/compiler/didYouMeanStringLiteral.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 721e8aab3c8..115e2447360 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17831,6 +17831,13 @@ namespace ts { 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; } else { + if (source.flags & TypeFlags.StringLiteral && target.flags & TypeFlags.Union) { + const suggestedType = getSuggestedTypeForNonexistentStringLiteralType(source as StringLiteralType, target as UnionType); + if (suggestedType) { + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_Did_you_mean_2, generalizedSourceType, targetType, typeToString(suggestedType)); + return; + } + } message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } @@ -28230,6 +28237,11 @@ namespace ts { return suggestion; } + function getSuggestedTypeForNonexistentStringLiteralType(source: StringLiteralType, target: UnionType): StringLiteralType | undefined { + const candidates = target.types.filter((type): type is StringLiteralType => !!(type.flags & TypeFlags.StringLiteral)); + return getSpellingSuggestion(source.value, candidates, type => type.value); + } + /** * 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. * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 21fab262595..8c10db3a8bf 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3300,6 +3300,10 @@ "category": "Error", "code": 2819 }, + "Type '{0}' is not assignable to type '{1}'. Did you mean '{2}'?": { + "category": "Error", + "code": 2820 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/didYouMeanStringLiteral.errors.txt b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt new file mode 100644 index 00000000000..1f96007277f --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.errors.txt @@ -0,0 +1,20 @@ +tests/cases/compiler/didYouMeanStringLiteral.ts(5,7): error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'? +tests/cases/compiler/didYouMeanStringLiteral.ts(6,7): error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. +tests/cases/compiler/didYouMeanStringLiteral.ts(7,7): error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'? + + +==== tests/cases/compiler/didYouMeanStringLiteral.ts (3 errors) ==== + type T1 = "string" | "number" | "boolean"; + type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" + type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + + const t1: T1 = "strong"; + ~~ +!!! error TS2820: Type '"strong"' is not assignable to type 'T1'. Did you mean '"string"'? + const t2: T2 = "strong"; + ~~ +!!! error TS2322: Type '"strong"' is not assignable to type '"number" | "boolean"'. + const t3: T3 = "strong"; + ~~ +!!! error TS2820: Type '"strong"' is not assignable to type '"string" | "boolean"'. Did you mean '"string"'? + \ No newline at end of file diff --git a/tests/baselines/reference/didYouMeanStringLiteral.js b/tests/baselines/reference/didYouMeanStringLiteral.js new file mode 100644 index 00000000000..b1b356c74a1 --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.js @@ -0,0 +1,14 @@ +//// [didYouMeanStringLiteral.ts] +type T1 = "string" | "number" | "boolean"; +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + +const t1: T1 = "strong"; +const t2: T2 = "strong"; +const t3: T3 = "strong"; + + +//// [didYouMeanStringLiteral.js] +var t1 = "strong"; +var t2 = "strong"; +var t3 = "strong"; diff --git a/tests/baselines/reference/didYouMeanStringLiteral.symbols b/tests/baselines/reference/didYouMeanStringLiteral.symbols new file mode 100644 index 00000000000..e0ab68cfc6e --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.symbols @@ -0,0 +1,24 @@ +=== tests/cases/compiler/didYouMeanStringLiteral.ts === +type T1 = "string" | "number" | "boolean"; +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" +>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +const t1: T1 = "strong"; +>t1 : Symbol(t1, Decl(didYouMeanStringLiteral.ts, 4, 5)) +>T1 : Symbol(T1, Decl(didYouMeanStringLiteral.ts, 0, 0)) + +const t2: T2 = "strong"; +>t2 : Symbol(t2, Decl(didYouMeanStringLiteral.ts, 5, 5)) +>T2 : Symbol(T2, Decl(didYouMeanStringLiteral.ts, 0, 42)) + +const t3: T3 = "strong"; +>t3 : Symbol(t3, Decl(didYouMeanStringLiteral.ts, 6, 5)) +>T3 : Symbol(T3, Decl(didYouMeanStringLiteral.ts, 1, 38)) + diff --git a/tests/baselines/reference/didYouMeanStringLiteral.types b/tests/baselines/reference/didYouMeanStringLiteral.types new file mode 100644 index 00000000000..c348534c652 --- /dev/null +++ b/tests/baselines/reference/didYouMeanStringLiteral.types @@ -0,0 +1,22 @@ +=== tests/cases/compiler/didYouMeanStringLiteral.ts === +type T1 = "string" | "number" | "boolean"; +>T1 : T1 + +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +>T2 : "number" | "boolean" + +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" +>T3 : "string" | "boolean" + +const t1: T1 = "strong"; +>t1 : T1 +>"strong" : "strong" + +const t2: T2 = "strong"; +>t2 : "number" | "boolean" +>"strong" : "strong" + +const t3: T3 = "strong"; +>t3 : "string" | "boolean" +>"strong" : "strong" + diff --git a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt index 93996556e65..70498f76d63 100644 --- a/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt +++ b/tests/baselines/reference/errorsForCallAndAssignmentAreSimilar.errors.txt @@ -1,5 +1,5 @@ -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. -tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(11,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? +tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? ==== tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts (2 errors) ==== @@ -15,7 +15,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS232 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]); @@ -23,7 +23,7 @@ tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts(16,11): error TS232 { kind: "bluray", }, { kind: "hdpvd", } ~~~~ -!!! error TS2322: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. +!!! error TS2820: Type '"hdpvd"' is not assignable to type '"hddvd" | "bluray"'. Did you mean '"hddvd"'? !!! related TS6500 tests/cases/compiler/errorsForCallAndAssignmentAreSimilar.ts:3:13: The expected type comes from property 'kind' which is declared here on type 'Disc' ]; } \ No newline at end of file diff --git a/tests/cases/compiler/didYouMeanStringLiteral.ts b/tests/cases/compiler/didYouMeanStringLiteral.ts new file mode 100644 index 00000000000..7110507de8b --- /dev/null +++ b/tests/cases/compiler/didYouMeanStringLiteral.ts @@ -0,0 +1,7 @@ +type T1 = "string" | "number" | "boolean"; +type T2 = T1 & ("number" | "boolean"); // "number" | "boolean" +type T3 = T1 & ("string" | "boolean"); // "string" | "boolean" + +const t1: T1 = "strong"; +const t2: T2 = "strong"; +const t3: T3 = "strong";