diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 82b2c46801..da6d6035f5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8845,7 +8845,15 @@ namespace ts { error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); } else { - error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType)); + let suggestion: string | undefined; + if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { + if (suggestion !== undefined) { + error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); + } + } + else { + error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType)); + } } } return anyType; @@ -17367,14 +17375,21 @@ namespace ts { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestion); } else { - errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + const suggestion = getSuggestionForNonexistentProperty(propNode, containingType); + if (suggestion !== undefined) { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, declarationNameToString(propNode), typeToString(containingType), suggestion); + } + else { + errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); + } } } + diagnostics.add(createDiagnosticForNodeFromMessageChain(propNode, errorInfo)); } - function getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined { - const suggestion = getSpellingSuggestionForName(idText(node), getPropertiesOfType(containingType), SymbolFlags.Value); + function getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined { + const suggestion = getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value); return suggestion && symbolName(suggestion); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7cd4e50365..9a776ea097 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2976,7 +2976,7 @@ namespace ts { */ /* @internal */ tryGetMemberInModuleExportsAndProperties(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; + getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a7d4b73c53..2bd0930e4d 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2659,7 +2659,7 @@ declare namespace ts { */ tryGetMemberInModuleExportsAndProperties(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; + getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 86215a7b7b..2195a9d7d7 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1904,7 +1904,7 @@ declare namespace ts { getAmbientModules(): Symbol[]; tryGetMemberInModuleExports(memberName: string, moduleSymbol: Symbol): Symbol | undefined; getApparentType(type: Type): Type; - getSuggestionForNonexistentProperty(node: Identifier, containingType: Type): string | undefined; + getSuggestionForNonexistentProperty(name: Identifier | string, containingType: Type): string | undefined; getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; getSuggestionForNonexistentModule(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; diff --git a/tests/baselines/reference/indexedAccessImplicitlyAny.errors.txt b/tests/baselines/reference/indexedAccessImplicitlyAny.errors.txt new file mode 100644 index 0000000000..ae950b5e69 --- /dev/null +++ b/tests/baselines/reference/indexedAccessImplicitlyAny.errors.txt @@ -0,0 +1,13 @@ +tests/cases/compiler/indexedAccessImplicitlyAny.ts(3,3): error TS2551: Property 'foo' does not exist on type 'I'. Did you mean 'foof'? +tests/cases/compiler/indexedAccessImplicitlyAny.ts(4,3): error TS2551: Property 'foo' does not exist on type 'I'. Did you mean 'foof'? + + +==== tests/cases/compiler/indexedAccessImplicitlyAny.ts (2 errors) ==== + interface I { foof: number }; + declare const i: I; + i.foo; + ~~~ +!!! error TS2551: Property 'foo' does not exist on type 'I'. Did you mean 'foof'? + i["foo"]; + ~~~~~ +!!! error TS2551: Property 'foo' does not exist on type 'I'. Did you mean 'foof'? \ No newline at end of file diff --git a/tests/baselines/reference/indexedAccessImplicitlyAny.js b/tests/baselines/reference/indexedAccessImplicitlyAny.js new file mode 100644 index 0000000000..250f6d8846 --- /dev/null +++ b/tests/baselines/reference/indexedAccessImplicitlyAny.js @@ -0,0 +1,10 @@ +//// [indexedAccessImplicitlyAny.ts] +interface I { foof: number }; +declare const i: I; +i.foo; +i["foo"]; + +//// [indexedAccessImplicitlyAny.js] +; +i.foo; +i["foo"]; diff --git a/tests/baselines/reference/indexedAccessImplicitlyAny.symbols b/tests/baselines/reference/indexedAccessImplicitlyAny.symbols new file mode 100644 index 0000000000..7263bc6aa5 --- /dev/null +++ b/tests/baselines/reference/indexedAccessImplicitlyAny.symbols @@ -0,0 +1,15 @@ +=== tests/cases/compiler/indexedAccessImplicitlyAny.ts === +interface I { foof: number }; +>I : Symbol(I, Decl(indexedAccessImplicitlyAny.ts, 0, 0)) +>foof : Symbol(I.foof, Decl(indexedAccessImplicitlyAny.ts, 0, 13)) + +declare const i: I; +>i : Symbol(i, Decl(indexedAccessImplicitlyAny.ts, 1, 13)) +>I : Symbol(I, Decl(indexedAccessImplicitlyAny.ts, 0, 0)) + +i.foo; +>i : Symbol(i, Decl(indexedAccessImplicitlyAny.ts, 1, 13)) + +i["foo"]; +>i : Symbol(i, Decl(indexedAccessImplicitlyAny.ts, 1, 13)) + diff --git a/tests/baselines/reference/indexedAccessImplicitlyAny.types b/tests/baselines/reference/indexedAccessImplicitlyAny.types new file mode 100644 index 0000000000..15f69b8931 --- /dev/null +++ b/tests/baselines/reference/indexedAccessImplicitlyAny.types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/indexedAccessImplicitlyAny.ts === +interface I { foof: number }; +>I : I +>foof : number + +declare const i: I; +>i : I +>I : I + +i.foo; +>i.foo : any +>i : I +>foo : any + +i["foo"]; +>i["foo"] : any +>i : I +>"foo" : "foo" + diff --git a/tests/cases/compiler/indexedAccessImplicitlyAny.ts b/tests/cases/compiler/indexedAccessImplicitlyAny.ts new file mode 100644 index 0000000000..e01a7d45dc --- /dev/null +++ b/tests/cases/compiler/indexedAccessImplicitlyAny.ts @@ -0,0 +1,7 @@ +// @target: esnext +// @noImplicitAny: true + +interface I { foof: number }; +declare const i: I; +i.foo; +i["foo"]; \ No newline at end of file