From 278a5b80a8821f9630b3ef1b76b433494b0d650d Mon Sep 17 00:00:00 2001 From: Andrey Cherkasov Date: Thu, 13 Apr 2023 03:07:23 +0400 Subject: [PATCH] [devkit] LightServiceMustBeFinal: suggest removing the 'Service' annotation on abstract classes and interfaces IJ-CR-102194 GitOrigin-RevId: d7bf3203b32d9da35df621ed7cdc5c4c68c775c8 --- .../jvm/actions/JvmElementActionsFactory.kt | 2 + .../com/intellij/lang/jvm/actions/actions.kt | 6 +++ .../src/messages/QuickFixBundle.properties | 3 ++ .../impl/JavaElementActionsFactory.kt | 37 ++++++++++++++--- .../LightServiceMustBeFinal.html | 19 ++++++++- .../messages/DevKitBundle.properties | 8 ++++ .../LightServiceMustBeFinalInspection.kt | 30 ++++++++++---- .../AbstractClass.java | 7 ++++ .../AbstractClass_after.java | 2 + .../lightServiceMustBeFinal/Interface.java | 7 ++++ .../Interface_after.java | 2 + .../LightServiceMustBeFinalInspectionTest.kt | 5 +++ .../lightServiceMustBeFinal/AbstractClass.kt | 6 +++ .../AbstractClass_after.kt | 1 + .../lightServiceMustBeFinal/Interface.kt | 6 +++ .../Interface_after.kt | 1 + ...KtLightServiceMustBeFinalInspectionTest.kt | 9 ++-- .../KotlinElementActionsFactory.kt | 41 +++++++++++++++++++ 18 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.java create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.kt create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.kt create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface.kt create mode 100644 plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.kt diff --git a/java/java-analysis-api/src/com/intellij/lang/jvm/actions/JvmElementActionsFactory.kt b/java/java-analysis-api/src/com/intellij/lang/jvm/actions/JvmElementActionsFactory.kt index bda7ef684d62..4f54a0e17048 100644 --- a/java/java-analysis-api/src/com/intellij/lang/jvm/actions/JvmElementActionsFactory.kt +++ b/java/java-analysis-api/src/com/intellij/lang/jvm/actions/JvmElementActionsFactory.kt @@ -21,6 +21,8 @@ abstract class JvmElementActionsFactory { open fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List = emptyList() + open fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List = emptyList() + open fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation, attributeIndex: Int, request: AnnotationAttributeRequest): List = emptyList() diff --git a/java/java-analysis-api/src/com/intellij/lang/jvm/actions/actions.kt b/java/java-analysis-api/src/com/intellij/lang/jvm/actions/actions.kt index 5751f834fe03..3b04eef867fe 100644 --- a/java/java-analysis-api/src/com/intellij/lang/jvm/actions/actions.kt +++ b/java/java-analysis-api/src/com/intellij/lang/jvm/actions/actions.kt @@ -35,6 +35,12 @@ fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationReq } } +fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List { + return createActions { + it.createRemoveAnnotationActions(target, request) + } +} + fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation, attributeIndex: Int, request: AnnotationAttributeRequest): List { diff --git a/java/java-analysis-impl/src/messages/QuickFixBundle.properties b/java/java-analysis-impl/src/messages/QuickFixBundle.properties index fd1be900ee60..16f7a8784b5e 100644 --- a/java/java-analysis-impl/src/messages/QuickFixBundle.properties +++ b/java/java-analysis-impl/src/messages/QuickFixBundle.properties @@ -171,6 +171,9 @@ remove.modifier.fix.family=Make not {0} remove.override.fix.family=Remove override remove.override.fix.text=Remove override annotation from method declaration +remove.annotation.fix.family=Remove annotation +remove.annotation.fix.text=Remove ''@{0}'' annotation + change.inheritors.visibility.warning.text=Do you want to change inheritors' visibility to visibility of the base method? change.inheritors.visibility.warning.title=Change Inheritors move.class.in.extend.list.family=Move Class in Extend list diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt b/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt index d0da10f33179..47ba449859c1 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/JavaElementActionsFactory.kt @@ -7,13 +7,17 @@ import com.intellij.codeInsight.intention.AddAnnotationPsiFix import com.intellij.codeInsight.intention.FileModifier import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo +import com.intellij.codeInspection.util.IntentionFamilyName +import com.intellij.codeInspection.util.IntentionName import com.intellij.lang.java.JavaLanguage import com.intellij.lang.java.actions.* import com.intellij.lang.jvm.* import com.intellij.lang.jvm.actions.* import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtilRt import com.intellij.psi.* +import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil import com.intellij.refactoring.suggested.createSmartPointer @@ -28,14 +32,17 @@ class JavaElementActionsFactory : JvmElementActionsFactory() { return listOf(ChangeModifierFix(declaration, request)) } - private class RemoveAnnotationFix(private val fqn: String, element: PsiModifierListOwner) : IntentionAction { + private open class RemoveAnnotationFix(private val fqn: String, + element: PsiModifierListOwner, + @IntentionName private val text: String, + @IntentionFamilyName private val familyName: String) : IntentionAction { val pointer = element.createSmartPointer() override fun startInWriteAction(): Boolean = true - override fun getText(): String = QuickFixBundle.message("remove.override.fix.text") + override fun getText(): String = text - override fun getFamilyName(): String = QuickFixBundle.message("remove.override.fix.family") + override fun getFamilyName(): String = familyName override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = pointer.element != null @@ -50,16 +57,27 @@ class JavaElementActionsFactory : JvmElementActionsFactory() { private fun PsiModifierListOwner.deleteAnnotation() { getAnnotation(fqn)?.delete() + val file = this.containingFile as? PsiJavaFile ?: return + JavaCodeStyleManager.getInstance(project).removeRedundantImports(file) } } + private class RemoveOverrideAnnotationFix(element: PsiModifierListOwner) : + RemoveAnnotationFix( + CommonClassNames.JAVA_LANG_OVERRIDE, + element, + QuickFixBundle.message("remove.override.fix.text"), + QuickFixBundle.message("remove.override.fix.family") + ) + override fun createChangeOverrideActions(target: JvmModifiersOwner, shouldBePresent: Boolean): List { val psiElement = target.asSafely() ?: return emptyList() if (psiElement.language != JavaLanguage.INSTANCE) return emptyList() return if (shouldBePresent) { createAddAnnotationActions(target, annotationRequest(CommonClassNames.JAVA_LANG_OVERRIDE)) - } else { - listOf(RemoveAnnotationFix(CommonClassNames.JAVA_LANG_OVERRIDE, psiElement)) + } + else { + listOf(RemoveOverrideAnnotationFix(psiElement)) } } @@ -85,6 +103,15 @@ class JavaElementActionsFactory : JvmElementActionsFactory() { return listOf(CreateAnnotationAction(declaration, request)) } + override fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List { + val declaration = target as? PsiModifierListOwner ?: return emptyList() + if (declaration.language != JavaLanguage.INSTANCE) return emptyList() + val shortName = StringUtilRt.getShortName(request.qualifiedName) + val text = QuickFixBundle.message("remove.annotation.fix.text", shortName) + val familyName = QuickFixBundle.message("remove.annotation.fix.family") + return listOf(RemoveAnnotationFix(request.qualifiedName, target, text, familyName)) + } + override fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation, attributeIndex: Int, request: AnnotationAttributeRequest): List { diff --git a/plugins/devkit/devkit-core/resources/inspectionDescriptions/LightServiceMustBeFinal.html b/plugins/devkit/devkit-core/resources/inspectionDescriptions/LightServiceMustBeFinal.html index 3a6a23f6d8a4..9688a05190bf 100644 --- a/plugins/devkit/devkit-core/resources/inspectionDescriptions/LightServiceMustBeFinal.html +++ b/plugins/devkit/devkit-core/resources/inspectionDescriptions/LightServiceMustBeFinal.html @@ -1,19 +1,34 @@ Reports classes annotated with the @com.intellij.openapi.components.Service annotation that are not final. -

Suggests making a class final.

+

+ Suggests making a class final if it is concrete.

Example:


   // MyService.kt
   @Service(Service.Level.APP)
   open class MyService
 
-

After the quick fix is applied:

+

After the quick-fix is applied:


   // MyService.kt
   @Service(Service.Level.APP)
   class MyService
 
+

+ Suggests removing the @Service annotation if it is an abstract class or interface. +

+

Example:

+

+  // MyService.java
+  @Service(Service.Level.APP)
+  abstract class MyService {}
+
+

After the quick-fix is applied:

+

+  // MyService.java
+  abstract class MyService {}
+

New in 2023.2 \ No newline at end of file diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index e9f71602dc58..26b0cb37bda8 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -618,6 +618,14 @@ inspection.message.obsolete.api.used=Obsolete API is used inspection.light.service.must.be.final.display.name=Light service must be final inspection.light.service.must.be.final.message=Light service must be final inspection.light.service.must.not.be.open.message=Light service must not be open +inspection.light.service.must.be.concrete.class.message=\ + Light service must be a concrete class and cannot be abstract or an interface.\n\ + The IntelliJ Platform relies on the concrete implementation class to create and \ + manage the service instance. Without a concrete implementation, the platform \ + would not be able to create an instance of the service, and the service would \ + not be available for use by the plugin.\n\ + To solve this problem, you should define a concrete implementation class for the \ + service and annotate it with 'com.intellij.openapi.components.Service'. inspection.mismatched.light.service.level.and.ctor.display.name=Mismatch between light service level and its constructor inspection.mismatched.light.service.level.and.ctor.project.level.required=Light service with a constructor that takes a single parameter of type 'com.intellij.openapi.project.Project' must specify '@Service(Service.Level.PROJECT)' diff --git a/plugins/devkit/devkit-core/src/inspections/LightServiceMustBeFinalInspection.kt b/plugins/devkit/devkit-core/src/inspections/LightServiceMustBeFinalInspection.kt index bd50efd9248c..b6a98a5ebee7 100644 --- a/plugins/devkit/devkit-core/src/inspections/LightServiceMustBeFinalInspection.kt +++ b/plugins/devkit/devkit-core/src/inspections/LightServiceMustBeFinalInspection.kt @@ -3,8 +3,13 @@ package org.jetbrains.idea.devkit.inspections import com.intellij.codeInspection.IntentionWrapper import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.lang.jvm.* +import com.intellij.lang.jvm.DefaultJvmElementVisitor +import com.intellij.lang.jvm.JvmClass +import com.intellij.lang.jvm.JvmElementVisitor +import com.intellij.lang.jvm.JvmModifier +import com.intellij.lang.jvm.actions.annotationRequest import com.intellij.lang.jvm.actions.createModifierActions +import com.intellij.lang.jvm.actions.createRemoveAnnotationActions import com.intellij.lang.jvm.actions.modifierRequest import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project @@ -16,14 +21,23 @@ internal class LightServiceMustBeFinalInspection : DevKitJvmInspection() { override fun buildVisitor(project: Project, sink: HighlightSink, isOnTheFly: Boolean): JvmElementVisitor { return object : DefaultJvmElementVisitor { override fun visitClass(clazz: JvmClass): Boolean { - if (clazz !is PsiClass) return true - if (clazz.classKind != JvmClassKind.CLASS || clazz.hasModifier(JvmModifier.FINAL)) return true - val file = clazz.sourceElement?.containingFile ?: return true - val hasServiceAnnotation = clazz.hasAnnotation(Service::class.java.canonicalName) - if (hasServiceAnnotation) { - val actions = createModifierActions(clazz, modifierRequest(JvmModifier.FINAL, true)) + val sourceElement = clazz.sourceElement + if (sourceElement !is PsiClass) return true + if (sourceElement.isAnnotationType || sourceElement.isEnum || sourceElement.hasModifier(JvmModifier.FINAL)) return true + val file = sourceElement.containingFile ?: return true + val serviceAnnotation = sourceElement.getAnnotation(Service::class.java.canonicalName) ?: return true + val elementToReport = serviceAnnotation.nameReferenceElement ?: return true + if (sourceElement.isInterface || sourceElement.hasModifier(JvmModifier.ABSTRACT)) { + val actions = createRemoveAnnotationActions(sourceElement, annotationRequest(Service::class.java.canonicalName)) val fixes = IntentionWrapper.wrapToQuickFixes(actions.toTypedArray(), file) - val message = when (clazz.language.id) { + val message = DevKitBundle.message("inspection.light.service.must.be.concrete.class.message") + val holder = (sink as HighlightSinkImpl).holder + holder.registerProblem(elementToReport, message, ProblemHighlightType.GENERIC_ERROR, *fixes) + } + else { + val actions = createModifierActions(sourceElement, modifierRequest(JvmModifier.FINAL, true)) + val fixes = IntentionWrapper.wrapToQuickFixes(actions.toTypedArray(), file) + val message = when (sourceElement.language.id) { "kotlin" -> DevKitBundle.message("inspection.light.service.must.not.be.open.message") else -> DevKitBundle.message("inspection.light.service.must.be.final.message") } diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.java b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.java new file mode 100644 index 000000000000..e0117385ee11 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.java @@ -0,0 +1,7 @@ +import com.intellij.openapi.components.Service; + +@Service +abstract class MyService { +} diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.java new file mode 100644 index 000000000000..8ce87604ec75 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.java @@ -0,0 +1,2 @@ +abstract class MyService { +} diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface.java b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface.java new file mode 100644 index 000000000000..05ef6b5e6e91 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface.java @@ -0,0 +1,7 @@ +import com.intellij.openapi.components.Service; + +@Service +interface MyService { +} diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.java b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.java new file mode 100644 index 000000000000..be653da61fc2 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.java @@ -0,0 +1,2 @@ +interface MyService { +} diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/LightServiceMustBeFinalInspectionTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/LightServiceMustBeFinalInspectionTest.kt index 4e998aa1f2be..bf56215a35c1 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/LightServiceMustBeFinalInspectionTest.kt +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/LightServiceMustBeFinalInspectionTest.kt @@ -2,6 +2,7 @@ package org.jetbrains.idea.devkit.inspections import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.openapi.components.Service import com.intellij.testFramework.TestDataPath import org.jetbrains.idea.devkit.DevkitJavaTestsUtil import org.jetbrains.idea.devkit.inspections.quickfix.LightServiceMustBeFinalInspectionTestBase @@ -17,4 +18,8 @@ internal class LightServiceMustBeFinalInspectionTest : LightServiceMustBeFinalIn fun testMakeFinal() { doTest(MAKE_FINAL_FIX_NAME) } fun testMakeFinalMultiLineModifierList() { doTest(MAKE_FINAL_FIX_NAME) } + + fun testAbstractClass() { doTest(QuickFixBundle.message("remove.annotation.fix.text", Service::class.java.simpleName)) } + + fun testInterface() { doTest(QuickFixBundle.message("remove.annotation.fix.text", Service::class.java.simpleName)) } } diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.kt new file mode 100644 index 000000000000..b126866b9414 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass.kt @@ -0,0 +1,6 @@ +import com.intellij.openapi.components.Service + +@Service +abstract class MyService \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.kt new file mode 100644 index 000000000000..58128db35a7a --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/AbstractClass_after.kt @@ -0,0 +1 @@ +abstract class MyService \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface.kt new file mode 100644 index 000000000000..b6bbf522cbcf --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface.kt @@ -0,0 +1,6 @@ +import com.intellij.openapi.components.Service + +@Service +interface MyService \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.kt b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.kt new file mode 100644 index 000000000000..1e3b3fa60988 --- /dev/null +++ b/plugins/devkit/devkit-kotlin-tests/testData/inspections/lightServiceMustBeFinal/Interface_after.kt @@ -0,0 +1 @@ +interface MyService \ No newline at end of file diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtLightServiceMustBeFinalInspectionTest.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtLightServiceMustBeFinalInspectionTest.kt index 6d3ea748135a..50be271dd312 100644 --- a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtLightServiceMustBeFinalInspectionTest.kt +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/KtLightServiceMustBeFinalInspectionTest.kt @@ -2,6 +2,7 @@ package org.jetbrains.idea.devkit.kotlin.inspections import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.openapi.components.Service import com.intellij.testFramework.TestDataPath import org.jetbrains.idea.devkit.inspections.quickfix.LightServiceMustBeFinalInspectionTestBase import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil @@ -15,7 +16,9 @@ internal class KtLightServiceMustBeFinalInspectionTest : LightServiceMustBeFinal override fun getFileExtension() = "kt" - fun testMakeNotOpen() { - doTest(fixName) - } + fun testMakeNotOpen() { doTest(fixName) } + + fun testAbstractClass() { doTest(QuickFixBundle.message("remove.annotation.fix.text", Service::class.java.simpleName)) } + + fun testInterface() { doTest(QuickFixBundle.message("remove.annotation.fix.text", Service::class.java.simpleName)) } } diff --git a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt index 501d5d89b2d2..aa3556784615 100644 --- a/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt +++ b/plugins/kotlin/idea/src/org/jetbrains/kotlin/idea/quickfix/crossLanguage/KotlinElementActionsFactory.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.TypeIn import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.util.CommentSaver import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.findAnnotation import org.jetbrains.kotlin.idea.util.resolveToKotlinType import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.lexer.KtModifierKeywordToken @@ -171,6 +172,13 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() { return createChangeModifierActions(kModifierOwner, KtTokens.OVERRIDE_KEYWORD, shouldBePresent) } + override fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List { + val declaration = target.safeAs>()?.kotlinOrigin.safeAs()?.takeIf { + it.language == KotlinLanguage.INSTANCE + } ?: return emptyList() + return listOf(RemoveAnnotationAction(declaration, request)) + } + override fun createChangeModifierActions(target: JvmModifiersOwner, request: ChangeModifierRequest): List { val kModifierOwner = target.toKtElement() ?: return emptyList() @@ -492,6 +500,39 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() { } } + private class RemoveAnnotationAction(target: KtModifierListOwner, val request: AnnotationRequest) : IntentionAction { + + private val pointer = target.createSmartPointer() + + override fun startInWriteAction(): Boolean = true + + override fun getText(): String { + val shortName = StringUtilRt.getShortName(request.qualifiedName) + return QuickFixBundle.message("remove.annotation.fix.text", shortName) + } + + override fun getFamilyName(): String = QuickFixBundle.message("remove.annotation.fix.family") + + override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean = pointer.element != null + + override fun generatePreview(project: Project, editor: Editor, file: PsiFile): IntentionPreviewInfo { + PsiTreeUtil.findSameElementInCopy(pointer.element, file)?.removeAnnotation() + return IntentionPreviewInfo.DIFF + } + + override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { + pointer.element?.removeAnnotation() + } + + private fun KtModifierListOwner.removeAnnotation() { + val annotationName = FqName(request.qualifiedName) + val annotation = this.findAnnotation(annotationName) + annotation?.delete() ?: return + val importList = (this.containingFile as? KtFile)?.importList + importList?.imports?.find { it.importedFqName == annotationName }?.delete() + } + } + override fun createChangeParametersActions(target: JvmMethod, request: ChangeParametersRequest): List { return when (val kotlinOrigin = (target as? KtLightElement<*, *>)?.kotlinOrigin) { is KtNamedFunction -> listOfNotNull(ChangeMethodParameters.create(kotlinOrigin, request))