mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[devkit] LightServiceMustBeFinal: suggest removing the 'Service' annotation on abstract classes and interfaces
IJ-CR-102194 GitOrigin-RevId: d7bf3203b32d9da35df621ed7cdc5c4c68c775c8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1c3d684b64
commit
278a5b80a8
@@ -21,6 +21,8 @@ abstract class JvmElementActionsFactory {
|
||||
|
||||
open fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
open fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
open fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
@@ -35,6 +35,12 @@ fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationReq
|
||||
}
|
||||
}
|
||||
|
||||
fun createRemoveAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> {
|
||||
return createActions {
|
||||
it.createRemoveAnnotationActions(target, request)
|
||||
}
|
||||
}
|
||||
|
||||
fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> {
|
||||
|
||||
@@ -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
|
||||
|
||||
+32
-5
@@ -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<IntentionAction> {
|
||||
val psiElement = target.asSafely<PsiModifierListOwner>() ?: 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<IntentionAction> {
|
||||
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<IntentionAction> {
|
||||
|
||||
+17
-2
@@ -1,19 +1,34 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports classes annotated with the <code>@com.intellij.openapi.components.Service</code> annotation that are not final.
|
||||
<p>Suggests making a class final.</p>
|
||||
<p>
|
||||
Suggests making a class final if it is concrete.</p>
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
// MyService.kt
|
||||
@Service(Service.Level.APP)
|
||||
open class MyService
|
||||
</code></pre>
|
||||
<p>After the quick fix is applied:</p>
|
||||
<p>After the quick-fix is applied:</p>
|
||||
<pre><code>
|
||||
// MyService.kt
|
||||
@Service(Service.Level.APP)
|
||||
class MyService
|
||||
</code></pre>
|
||||
<p>
|
||||
Suggests removing the <code>@Service</code> annotation if it is an abstract class or interface.
|
||||
</p>
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
// MyService.java
|
||||
@Service(Service.Level.APP)
|
||||
abstract class MyService {}
|
||||
</code></pre>
|
||||
<p>After the quick-fix is applied:</p>
|
||||
<pre><code>
|
||||
// MyService.java
|
||||
abstract class MyService {}
|
||||
</code></pre>
|
||||
<p><small>New in 2023.2</small>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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)'
|
||||
|
||||
@@ -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<Boolean> {
|
||||
return object : DefaultJvmElementVisitor<Boolean> {
|
||||
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")
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
|
||||
@<error descr="Light service must be a concrete class and cannot be abstract or an interface.
|
||||
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.
|
||||
To solve this problem, you should define a concrete implementation class for the service and annotate it with 'com.intellij.openapi.components.Service'.">Service<caret></error>
|
||||
abstract class MyService {
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
abstract class MyService {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
|
||||
@<error descr="Light service must be a concrete class and cannot be abstract or an interface.
|
||||
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.
|
||||
To solve this problem, you should define a concrete implementation class for the service and annotate it with 'com.intellij.openapi.components.Service'.">Service<caret></error>
|
||||
interface MyService {
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
interface MyService {
|
||||
}
|
||||
+5
@@ -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)) }
|
||||
}
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
|
||||
<error descr="Light service must be a concrete class and cannot be abstract or an interface.
|
||||
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.
|
||||
To solve this problem, you should define a concrete implementation class for the service and annotate it with 'com.intellij.openapi.components.Service'.">@Service<caret></error>
|
||||
abstract class MyService
|
||||
+1
@@ -0,0 +1 @@
|
||||
abstract class MyService
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
|
||||
<error descr="Light service must be a concrete class and cannot be abstract or an interface.
|
||||
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.
|
||||
To solve this problem, you should define a concrete implementation class for the service and annotate it with 'com.intellij.openapi.components.Service'.">@Service<caret></error>
|
||||
interface MyService
|
||||
+1
@@ -0,0 +1 @@
|
||||
interface MyService
|
||||
+6
-3
@@ -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)) }
|
||||
}
|
||||
|
||||
+41
@@ -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<IntentionAction> {
|
||||
val declaration = target.safeAs<KtLightElement<*, *>>()?.kotlinOrigin.safeAs<KtModifierListOwner>()?.takeIf {
|
||||
it.language == KotlinLanguage.INSTANCE
|
||||
} ?: return emptyList()
|
||||
return listOf(RemoveAnnotationAction(declaration, request))
|
||||
}
|
||||
|
||||
override fun createChangeModifierActions(target: JvmModifiersOwner, request: ChangeModifierRequest): List<IntentionAction> {
|
||||
val kModifierOwner = target.toKtElement<KtModifierListOwner>() ?: 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<IntentionAction> {
|
||||
return when (val kotlinOrigin = (target as? KtLightElement<*, *>)?.kotlinOrigin) {
|
||||
is KtNamedFunction -> listOfNotNull(ChangeMethodParameters.create(kotlinOrigin, request))
|
||||
|
||||
Reference in New Issue
Block a user