• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const errorCodes = [
4        Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code,
5        Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1.code,
6    ];
7    const fixId = "fixClassDoesntImplementInheritedAbstractMember";
8    registerCodeFix({
9        errorCodes,
10        getCodeActions(context) {
11            const { sourceFile, span } = context;
12            const changes = textChanges.ChangeTracker.with(context, t =>
13                addMissingMembers(getClass(sourceFile, span.start), sourceFile, context, t, context.preferences));
14            return changes.length === 0 ? undefined : [createCodeFixAction(fixId, changes, Diagnostics.Implement_inherited_abstract_class, fixId, Diagnostics.Implement_all_inherited_abstract_classes)];
15        },
16        fixIds: [fixId],
17        getAllCodeActions: context => {
18            const seenClassDeclarations = new Map<string, true>();
19            return codeFixAll(context, errorCodes, (changes, diag) => {
20                const classDeclaration = getClass(diag.file, diag.start);
21                if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) {
22                    addMissingMembers(classDeclaration, context.sourceFile, context, changes, context.preferences);
23                }
24            });
25        },
26    });
27
28    function getClass(sourceFile: SourceFile, pos: number): ClassLikeDeclaration {
29        // Token is the identifier in the case of a class declaration
30        // or the class keyword token in the case of a class expression.
31        const token = getTokenAtPosition(sourceFile, pos);
32        return cast(token.parent, isClassLike);
33    }
34
35    function addMissingMembers(classDeclaration: ClassLikeDeclaration, sourceFile: SourceFile, context: TypeConstructionContext, changeTracker: textChanges.ChangeTracker, preferences: UserPreferences): void {
36        const extendsNode = getEffectiveBaseTypeNode(classDeclaration)!;
37        const checker = context.program.getTypeChecker();
38        const instantiatedExtendsType = checker.getTypeAtLocation(extendsNode);
39
40        // Note that this is ultimately derived from a map indexed by symbol names,
41        // so duplicates cannot occur.
42        const abstractAndNonPrivateExtendsSymbols = checker.getPropertiesOfType(instantiatedExtendsType).filter(symbolPointsToNonPrivateAndAbstractMember);
43
44        const importAdder = createImportAdder(sourceFile, context.program, preferences, context.host);
45        createMissingMemberNodes(classDeclaration, abstractAndNonPrivateExtendsSymbols, sourceFile, context, preferences, importAdder, member => changeTracker.insertNodeAtClassStart(sourceFile, classDeclaration, member));
46        importAdder.writeFixes(changeTracker);
47    }
48
49    function symbolPointsToNonPrivateAndAbstractMember(symbol: Symbol): boolean {
50        // See `codeFixClassExtendAbstractProtectedProperty.ts` in https://github.com/Microsoft/TypeScript/pull/11547/files
51        // (now named `codeFixClassExtendAbstractPrivateProperty.ts`)
52        const flags = getSyntacticModifierFlags(first(symbol.getDeclarations()!));
53        return !(flags & ModifierFlags.Private) && !!(flags & ModifierFlags.Abstract);
54    }
55}
56