• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixName = "fixOverrideModifier";
4    const fixAddOverrideId = "fixAddOverrideModifier";
5    const fixRemoveOverrideId = "fixRemoveOverrideModifier";
6
7    type ClassElementLikeHasJSDoc =
8        | ConstructorDeclaration
9        | PropertyDeclaration
10        | MethodDeclaration
11        | GetAccessorDeclaration
12        | SetAccessorDeclaration
13        | ParameterPropertyDeclaration;
14
15    const errorCodes = [
16        Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code,
17        Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code,
18        Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code,
19        Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code,
20        Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code,
21        Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code,
22        Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code,
23        Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code,
24        Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code,
25    ];
26
27    interface ErrorCodeFixInfo {
28        descriptions: DiagnosticMessage;
29        fixId?: string | undefined;
30        fixAllDescriptions?: DiagnosticMessage | undefined;
31    }
32
33    const errorCodeFixIdMap: Record<number, ErrorCodeFixInfo> = {
34        // case #1:
35        [Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code]: {
36            descriptions: Diagnostics.Add_override_modifier,
37            fixId: fixAddOverrideId,
38            fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
39        },
40        [Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code]: {
41            descriptions: Diagnostics.Add_override_modifier,
42            fixId: fixAddOverrideId,
43            fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers
44        },
45        // case #2:
46        [Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code]: {
47            descriptions: Diagnostics.Remove_override_modifier,
48            fixId: fixRemoveOverrideId,
49            fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
50        },
51        [Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code]: {
52            descriptions: Diagnostics.Remove_override_modifier,
53            fixId: fixRemoveOverrideId,
54            fixAllDescriptions: Diagnostics.Remove_override_modifier
55        },
56        // case #3:
57        [Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code]: {
58            descriptions: Diagnostics.Add_override_modifier,
59            fixId: fixAddOverrideId,
60            fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
61        },
62        [Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code]: {
63            descriptions: Diagnostics.Add_override_modifier,
64            fixId: fixAddOverrideId,
65            fixAllDescriptions: Diagnostics.Add_all_missing_override_modifiers,
66        },
67        // case #4:
68        [Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code]: {
69            descriptions: Diagnostics.Add_override_modifier,
70            fixId: fixAddOverrideId,
71            fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
72        },
73        // case #5:
74        [Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code]: {
75            descriptions: Diagnostics.Remove_override_modifier,
76            fixId: fixRemoveOverrideId,
77            fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
78        },
79        [Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code]: {
80            descriptions: Diagnostics.Remove_override_modifier,
81            fixId: fixRemoveOverrideId,
82            fixAllDescriptions: Diagnostics.Remove_all_unnecessary_override_modifiers,
83        }
84    };
85
86    registerCodeFix({
87        errorCodes,
88        getCodeActions: function getCodeActionsToFixOverrideModifierIssues(context) {
89            const { errorCode, span } = context;
90
91            const info = errorCodeFixIdMap[errorCode];
92            if (!info) return emptyArray;
93
94            const { descriptions, fixId, fixAllDescriptions } = info;
95            const changes = textChanges.ChangeTracker.with(context, changes => dispatchChanges(changes, context, errorCode, span.start));
96
97            return [
98                createCodeFixActionMaybeFixAll(fixName, changes, descriptions, fixId, fixAllDescriptions)
99            ];
100        },
101        fixIds: [fixName, fixAddOverrideId, fixRemoveOverrideId],
102        getAllCodeActions: context =>
103            codeFixAll(context, errorCodes, (changes, diag) => {
104                const { code, start } = diag;
105                const info = errorCodeFixIdMap[code];
106                if (!info || info.fixId !== context.fixId) {
107                    return;
108                }
109
110                dispatchChanges(changes, context, code, start);
111            })
112    });
113
114    function dispatchChanges(
115        changeTracker: textChanges.ChangeTracker,
116        context: CodeFixContext | CodeFixAllContext,
117        errorCode: number,
118        pos: number) {
119        switch (errorCode) {
120            case Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0.code:
121            case Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code:
122            case Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0.code:
123            case Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0.code:
124            case Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0.code:
125                return doAddOverrideModifierChange(changeTracker, context.sourceFile, pos);
126            case Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0.code:
127            case Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0.code:
128            case Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class.code:
129            case Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class.code:
130                return doRemoveOverrideModifierChange(changeTracker, context.sourceFile, pos);
131            default:
132                Debug.fail("Unexpected error code: " + errorCode);
133        }
134    }
135
136    function doAddOverrideModifierChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
137        const classElement = findContainerClassElementLike(sourceFile, pos);
138        if (isSourceFileJS(sourceFile)) {
139            changeTracker.addJSDocTags(sourceFile, classElement, [factory.createJSDocOverrideTag(factory.createIdentifier("override"))]);
140            return;
141        }
142        const modifiers = classElement.modifiers || emptyArray;
143        const staticModifier = find(modifiers, isStaticModifier);
144        const abstractModifier = find(modifiers, isAbstractModifier);
145        const accessibilityModifier = find(modifiers, m => isAccessibilityModifier(m.kind));
146        const lastDecorator = findLast(modifiers, isDecorator);
147        const modifierPos = abstractModifier ? abstractModifier.end :
148            staticModifier ? staticModifier.end :
149            accessibilityModifier ? accessibilityModifier.end :
150            lastDecorator ? skipTrivia(sourceFile.text, lastDecorator.end) : classElement.getStart(sourceFile);
151        const options = accessibilityModifier || staticModifier || abstractModifier ? { prefix: " " } : { suffix: " " };
152        changeTracker.insertModifierAt(sourceFile, modifierPos, SyntaxKind.OverrideKeyword, options);
153    }
154
155    function doRemoveOverrideModifierChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
156        const classElement = findContainerClassElementLike(sourceFile, pos);
157        if (isSourceFileJS(sourceFile)) {
158            changeTracker.filterJSDocTags(sourceFile, classElement, not(isJSDocOverrideTag));
159            return;
160        }
161        const overrideModifier = find(classElement.modifiers, isOverrideModifier);
162        Debug.assertIsDefined(overrideModifier);
163
164        changeTracker.deleteModifier(sourceFile, overrideModifier);
165    }
166
167    function isClassElementLikeHasJSDoc(node: Node): node is ClassElementLikeHasJSDoc {
168        switch (node.kind) {
169            case SyntaxKind.Constructor:
170            case SyntaxKind.PropertyDeclaration:
171            case SyntaxKind.MethodDeclaration:
172            case SyntaxKind.GetAccessor:
173            case SyntaxKind.SetAccessor:
174                return true;
175            case SyntaxKind.Parameter:
176                return isParameterPropertyDeclaration(node, node.parent);
177            default:
178                return false;
179        }
180    }
181
182    function findContainerClassElementLike(sourceFile: SourceFile, pos: number) {
183        const token = getTokenAtPosition(sourceFile, pos);
184        const classElement = findAncestor(token, node => {
185            if (isClassLike(node)) return "quit";
186            return isClassElementLikeHasJSDoc(node);
187        });
188
189        Debug.assert(classElement && isClassElementLikeHasJSDoc(classElement));
190        return classElement;
191    }
192}
193
194