diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c1eb3ea2ddc..44cd5f06083 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -635,6 +635,7 @@ namespace ts { getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), getSuggestedSymbolForNonexistentModule, getSuggestionForNonexistentExport, + getSuggestedSymbolForNonexistentClassMember, getBaseConstraintOfType, getDefaultFromTypeParameter: type => type && type.flags & TypeFlags.TypeParameter ? getDefaultFromTypeParameter(type as TypeParameter) : undefined, resolveName(name, location, meaning, excludeGlobals) { @@ -27720,6 +27721,10 @@ namespace ts { } } + function getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined { + return getSpellingSuggestionForName(name, arrayFrom(getMembersOfSymbol(baseType.symbol).values()), SymbolFlags.ClassMember); + } + function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { let props = getPropertiesOfType(containingType); if (typeof name !== "string") { @@ -27739,7 +27744,7 @@ namespace ts { : strName === "class" ? find(properties, x => symbolName(x) === "className") : undefined; return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); - } + } function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); @@ -37346,7 +37351,7 @@ namespace ts { const baseClassName = typeToString(baseWithThis); if (prop && !baseProp && hasOverride) { - const suggestion = getSpellingSuggestionForName(symbolName(declaredProp), arrayFrom(getMembersOfSymbol(baseType.symbol).values()), SymbolFlags.ClassMember); + const suggestion = getSuggestedSymbolForNonexistentClassMember(symbolName(declaredProp), baseType); suggestion ? error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, baseClassName, symbolToString(suggestion)) : error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, baseClassName); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7df907413df..a7ef4448119 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4218,6 +4218,7 @@ namespace ts { /* @internal */ getSuggestedSymbolForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined; /* @internal */ getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined; /* @internal */ getSuggestedSymbolForNonexistentModule(node: Identifier, target: Symbol): Symbol | undefined; + /* @internal */ getSuggestedSymbolForNonexistentClassMember(name: string, baseType: Type): Symbol | undefined; /* @internal */ getSuggestionForNonexistentExport(node: Identifier, target: Symbol): string | undefined; getBaseConstraintOfType(type: Type): Type | undefined; getDefaultFromTypeParameter(type: Type): Type | undefined; diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts index 6d80e1f9008..885f5d7c461 100644 --- a/src/services/codefixes/fixSpelling.ts +++ b/src/services/codefixes/fixSpelling.ts @@ -7,6 +7,7 @@ namespace ts.codefix { Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code, Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2.code, + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1.code, // for JSX class components Diagnostics.No_overload_matches_this_call.code, // for JSX FC @@ -73,6 +74,14 @@ namespace ts.codefix { const props = checker.getContextualTypeForArgumentAtIndex(tag, 0); suggestedSymbol = checker.getSuggestedSymbolForNonexistentJSXAttribute(node, props!); } + else if (hasSyntacticModifier(parent, ModifierFlags.Override) && isClassElement(parent) && parent.name === node) { + const baseDeclaration = findAncestor(node, isClassLike); + const baseTypeNode = baseDeclaration ? getEffectiveBaseTypeNode(baseDeclaration) : undefined; + const baseType = baseTypeNode ? checker.getTypeAtLocation(baseTypeNode) : undefined; + if (baseType) { + suggestedSymbol = checker.getSuggestedSymbolForNonexistentClassMember(getTextOfNode(node), baseType); + } + } else { const meaning = getMeaningFromLocation(node); const name = getTextOfNode(node); diff --git a/tests/baselines/reference/override_js4.errors.txt b/tests/baselines/reference/override_js4.errors.txt new file mode 100644 index 00000000000..5b9b5b97f33 --- /dev/null +++ b/tests/baselines/reference/override_js4.errors.txt @@ -0,0 +1,15 @@ +tests/cases/conformance/override/a.js(7,5): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'A'. Did you mean 'doSomething'? + + +==== tests/cases/conformance/override/a.js (1 errors) ==== + class A { + doSomething() {} + } + + class B extends A { + /** @override */ + doSomethang() {} + ~~~~~~~~~~~ +!!! error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'A'. Did you mean 'doSomething'? + } + \ No newline at end of file diff --git a/tests/baselines/reference/override_js4.symbols b/tests/baselines/reference/override_js4.symbols new file mode 100644 index 00000000000..a35e801b73b --- /dev/null +++ b/tests/baselines/reference/override_js4.symbols @@ -0,0 +1,17 @@ +=== tests/cases/conformance/override/a.js === +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + doSomething() {} +>doSomething : Symbol(A.doSomething, Decl(a.js, 0, 9)) +} + +class B extends A { +>B : Symbol(B, Decl(a.js, 2, 1)) +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @override */ + doSomethang() {} +>doSomethang : Symbol(B.doSomethang, Decl(a.js, 4, 19)) +} + diff --git a/tests/baselines/reference/override_js4.types b/tests/baselines/reference/override_js4.types new file mode 100644 index 00000000000..270d34fafa2 --- /dev/null +++ b/tests/baselines/reference/override_js4.types @@ -0,0 +1,17 @@ +=== tests/cases/conformance/override/a.js === +class A { +>A : A + + doSomething() {} +>doSomething : () => void +} + +class B extends A { +>B : B +>A : A + + /** @override */ + doSomethang() {} +>doSomethang : () => void +} + diff --git a/tests/cases/conformance/override/override_js4.ts b/tests/cases/conformance/override/override_js4.ts new file mode 100644 index 00000000000..f5a7f1b6c41 --- /dev/null +++ b/tests/cases/conformance/override/override_js4.ts @@ -0,0 +1,14 @@ +// @noImplicitOverride: true +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @Filename: a.js +class A { + doSomething() {} +} + +class B extends A { + /** @override */ + doSomethang() {} +} diff --git a/tests/cases/fourslash/codeFixSpelling10.ts b/tests/cases/fourslash/codeFixSpelling10.ts new file mode 100644 index 00000000000..3f81eebeff1 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpelling10.ts @@ -0,0 +1,16 @@ +/// + +// @noImplicitOverride: true +////class A { +//// doSomething() {} +////} +//// +////class B extends A { +//// override [|doSomethang|]() {} +////} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Change_spelling_to_0.message, "doSomething"], + newRangeContent: "doSomething" +}); diff --git a/tests/cases/fourslash/codeFixSpelling11.ts b/tests/cases/fourslash/codeFixSpelling11.ts new file mode 100644 index 00000000000..dfea5370f01 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpelling11.ts @@ -0,0 +1,15 @@ +/// + +// @noImplicitOverride: true +////abstract class A { +//// abstract doSomething(): void; +////} +////abstract class B extends A { +//// abstract override [|doSomethang|](): number; +////} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Change_spelling_to_0.message, "doSomething"], + newRangeContent: "doSomething" +}); diff --git a/tests/cases/fourslash/codeFixSpelling12.ts b/tests/cases/fourslash/codeFixSpelling12.ts new file mode 100644 index 00000000000..0b44201f8e3 --- /dev/null +++ b/tests/cases/fourslash/codeFixSpelling12.ts @@ -0,0 +1,20 @@ +/// + +// @noImplicitOverride: true +// @allowJs: true +// @checkJs: true + +// @Filename: a.js +////class A { +//// doSomething() {} +////} +////class B extends A { +//// /** @override */ +//// [|doSomethang|]() {} +////} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Change_spelling_to_0.message, "doSomething"], + newRangeContent: "doSomething" +});