diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 29567db03d5..781f14566c9 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -32,6 +32,10 @@ namespace ts.codefix { return Debug.assertDefined(getContainingClass(getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false))); } + function symbolPointsToNonPrivateMember (symbol: Symbol) { + return !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private); + } + function addMissingDeclarations( checker: TypeChecker, implementedTypeNode: ExpressionWithTypeArguments, @@ -40,11 +44,12 @@ namespace ts.codefix { changeTracker: textChanges.ChangeTracker, preferences: UserPreferences, ): void { + const maybeHeritageClauseSymbol = getHeritageClauseSymbolTable(classDeclaration, checker); // Note that this is ultimately derived from a map indexed by symbol names, // so duplicates cannot occur. const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); - const nonPrivateMembers = implementedTypeSymbols.filter(symbol => !(getModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private)); + const nonPrivateAndNotExistedInHeritageClauseMembers = implementedTypeSymbols.filter(and(symbolPointsToNonPrivateMember, symbol => !maybeHeritageClauseSymbol.has(symbol.escapedName))); const classType = checker.getTypeAtLocation(classDeclaration)!; @@ -55,7 +60,7 @@ namespace ts.codefix { createMissingIndexSignatureDeclaration(implementedType, IndexKind.String); } - createMissingMemberNodes(classDeclaration, nonPrivateMembers, checker, preferences, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member)); + createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, checker, preferences, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member)); function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); @@ -64,4 +69,12 @@ namespace ts.codefix { } } } + + function getHeritageClauseSymbolTable (classDeclaration: ClassLikeDeclaration, checker: TypeChecker): SymbolTable { + const heritageClauseNode = getClassExtendsHeritageClauseElement(classDeclaration); + if (!heritageClauseNode) return createSymbolTable(); + const heritageClauseType = checker.getTypeAtLocation(heritageClauseNode) as InterfaceType; + const heritageClauseTypeSymbols = checker.getPropertiesOfType(heritageClauseType); + return createSymbolTable(heritageClauseTypeSymbols.filter(symbolPointsToNonPrivateMember)); + } } diff --git a/tests/cases/fourslash/codeFixClassImplementInterfaceHeritageClauseAlreadyHaveMember.ts b/tests/cases/fourslash/codeFixClassImplementInterfaceHeritageClauseAlreadyHaveMember.ts new file mode 100644 index 00000000000..b67bd966f9e --- /dev/null +++ b/tests/cases/fourslash/codeFixClassImplementInterfaceHeritageClauseAlreadyHaveMember.ts @@ -0,0 +1,39 @@ +/// + +//// class Base { +//// foo: number; +//// } +//// +//// class D extends Base { +//// bar: number; +//// } +//// +//// interface I { +//// foo: number; +//// bar: number; +//// baz: number; +//// } +//// +//// class C extends D implements I { } + +verify.codeFix({ + description: "Implement interface 'I'", + newFileContent: +`class Base { + foo: number; +} + +class D extends Base { + bar: number; +} + +interface I { + foo: number; + bar: number; + baz: number; +} + +class C extends D implements I { + baz: number; +}`, +});