• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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