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