1/* @internal */ 2namespace ts.codefix { 3 const errorCodes = [ 4 Diagnostics.Class_0_incorrectly_implements_interface_1.code, 5 Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code 6 ]; 7 const fixId = "fixClassIncorrectlyImplementsInterface"; // TODO: share a group with fixClassDoesntImplementInheritedAbstractMember? 8 registerCodeFix({ 9 errorCodes, 10 getCodeActions(context) { 11 const { sourceFile, span } = context; 12 const classDeclaration = getClass(sourceFile, span.start); 13 return mapDefined<ExpressionWithTypeArguments, CodeFixAction>(getEffectiveImplementsTypeNodes(classDeclaration), implementedTypeNode => { 14 const changes = textChanges.ChangeTracker.with(context, t => addMissingDeclarations(context, implementedTypeNode, sourceFile, classDeclaration, t, context.preferences)); 15 return changes.length === 0 ? undefined : createCodeFixAction(fixId, changes, [Diagnostics.Implement_interface_0, implementedTypeNode.getText(sourceFile)], fixId, Diagnostics.Implement_all_unimplemented_interfaces); 16 }); 17 }, 18 fixIds: [fixId], 19 getAllCodeActions(context) { 20 const seenClassDeclarations = new Map<string, true>(); 21 return codeFixAll(context, errorCodes, (changes, diag) => { 22 const classDeclaration = getClass(diag.file, diag.start); 23 if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { 24 for (const implementedTypeNode of getEffectiveImplementsTypeNodes(classDeclaration)!) { 25 addMissingDeclarations(context, implementedTypeNode, diag.file, classDeclaration, changes, context.preferences); 26 } 27 } 28 }); 29 }, 30 }); 31 32 function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration { 33 return Debug.checkDefined(getContainingClass(getTokenAtPosition(sourceFile, pos)), "There should be a containing class"); 34 } 35 36 function symbolPointsToNonPrivateMember(symbol: Symbol) { 37 return !symbol.valueDeclaration || !(getEffectiveModifierFlags(symbol.valueDeclaration) & ModifierFlags.Private); 38 } 39 40 function addMissingDeclarations( 41 context: TypeConstructionContext, 42 implementedTypeNode: ExpressionWithTypeArguments, 43 sourceFile: SourceFile, 44 classDeclaration: ClassLikeDeclaration, 45 changeTracker: textChanges.ChangeTracker, 46 preferences: UserPreferences, 47 ): void { 48 const checker = context.program.getTypeChecker(); 49 const maybeHeritageClauseSymbol = getHeritageClauseSymbolTable(classDeclaration, checker); 50 // Note that this is ultimately derived from a map indexed by symbol names, 51 // so duplicates cannot occur. 52 const implementedType = checker.getTypeAtLocation(implementedTypeNode) as InterfaceType; 53 const implementedTypeSymbols = checker.getPropertiesOfType(implementedType); 54 const nonPrivateAndNotExistedInHeritageClauseMembers = implementedTypeSymbols.filter(and(symbolPointsToNonPrivateMember, symbol => !maybeHeritageClauseSymbol.has(symbol.escapedName))); 55 56 const classType = checker.getTypeAtLocation(classDeclaration); 57 const constructor = find(classDeclaration.members, m => isConstructorDeclaration(m)); 58 59 if (!classType.getNumberIndexType()) { 60 createMissingIndexSignatureDeclaration(implementedType, IndexKind.Number); 61 } 62 if (!classType.getStringIndexType()) { 63 createMissingIndexSignatureDeclaration(implementedType, IndexKind.String); 64 } 65 66 const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host); 67 createMissingMemberNodes(classDeclaration, nonPrivateAndNotExistedInHeritageClauseMembers, sourceFile, context, preferences, importAdder, member => insertInterfaceMemberNode(sourceFile, classDeclaration, member)); 68 importAdder.writeFixes(changeTracker); 69 70 function createMissingIndexSignatureDeclaration(type: InterfaceType, kind: IndexKind): void { 71 const indexInfoOfKind = checker.getIndexInfoOfType(type, kind); 72 if (indexInfoOfKind) { 73 insertInterfaceMemberNode(sourceFile, classDeclaration, checker.indexInfoToIndexSignatureDeclaration(indexInfoOfKind, kind, classDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context))!); 74 } 75 } 76 77 // Either adds the node at the top of the class, or if there's a constructor right after that 78 function insertInterfaceMemberNode(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void { 79 if (constructor) { 80 changeTracker.insertNodeAfter(sourceFile, constructor, newElement); 81 } 82 else { 83 changeTracker.insertNodeAtClassStart(sourceFile, cls, newElement); 84 } 85 } 86 } 87 88 function getHeritageClauseSymbolTable(classDeclaration: ClassLikeDeclaration, checker: TypeChecker): SymbolTable { 89 const heritageClauseNode = getEffectiveBaseTypeNode(classDeclaration); 90 if (!heritageClauseNode) return createSymbolTable(); 91 const heritageClauseType = checker.getTypeAtLocation(heritageClauseNode) as InterfaceType; 92 const heritageClauseTypeSymbols = checker.getPropertiesOfType(heritageClauseType); 93 return createSymbolTable(heritageClauseTypeSymbols.filter(symbolPointsToNonPrivateMember)); 94 } 95} 96