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"
+});