[devkit] LightServiceMigrationInspection: provide quick-fix

...for the "Service can be converted to a light service" warning. The provided quick-fix annotates the service class with '@Service' and removes its registration from the 'plugin.xml' file.

^IDEA-254577

GitOrigin-RevId: 55cace08aa3beade8fb5e28532a29df459e2c1f0
This commit is contained in:
Andrey Cherkasov
2023-12-13 20:03:28 +00:00
committed by intellij-monorepo-bot
parent f596a23d56
commit e59d2a751e
33 changed files with 190 additions and 58 deletions
@@ -642,10 +642,7 @@ inspection.extension.class.should.be.final.text=Extension class should be final
inspection.extension.class.should.not.be.public.text=Extension class should not be public
inspection.light.service.migration.display.name=A service can be converted to a light one
inspection.light.service.migration.app.level.message=Service can be converted to a light one.\n\
Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file.
inspection.light.service.migration.project.level.message=Service can be converted to a light one.\n\
Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file.
inspection.light.service.migration.message=Service can be converted to a light service
inspection.light.service.migration.family.name=Convert to light service
inspection.light.service.migration.app.level.fix=Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file
inspection.light.service.migration.project.level.fix=Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file
@@ -2,13 +2,15 @@
package org.jetbrains.idea.devkit.inspections
import com.intellij.codeInspection.*
import com.intellij.lang.jvm.*
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.util.JvmInheritanceUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.util.xml.DomUtil
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.dom.Extension
import org.jetbrains.idea.devkit.inspections.quickfix.ConvertToLightServiceFix
import org.jetbrains.idea.devkit.util.locateExtensionsByPsiClass
import org.jetbrains.uast.UClass
@@ -26,7 +28,8 @@ internal class LightServiceMigrationCodeInspection : DevKitUastInspectionBase(UC
if (isVersion193OrHigher(psiClass) ||
ApplicationManager.getApplication().isUnitTestMode) {
if (isLightService(aClass)) return ProblemDescriptor.EMPTY_ARRAY
for (candidate in locateExtensionsByPsiClass(psiClass)) {
val extensionsCandidates = locateExtensionsByPsiClass(psiClass)
for (candidate in extensionsCandidates) {
val extension = DomUtil.findDomElement(candidate.pointer.element, Extension::class.java, false) ?: continue
val (serviceImplementation, level) = getServiceImplementation(extension) ?: continue
if (level == Service.Level.APP &&
@@ -34,7 +37,13 @@ internal class LightServiceMigrationCodeInspection : DevKitUastInspectionBase(UC
continue
}
if (serviceImplementation == psiClass && !containsUnitTestOrHeadlessModeCheck(aClass)) {
return registerProblem(aClass, level, manager, isOnTheFly)
val fixes = if (extensionsCandidates.size == 1) {
arrayOf<LocalQuickFix>(ConvertToLightServiceFix(psiClass, extension.xmlTag, level))
}
else {
LocalQuickFix.EMPTY_ARRAY
}
return registerProblem(aClass, manager, isOnTheFly, fixes)
}
}
}
@@ -42,12 +51,12 @@ internal class LightServiceMigrationCodeInspection : DevKitUastInspectionBase(UC
}
private fun registerProblem(aClass: UClass,
level: Service.Level,
manager: InspectionManager,
isOnTheFly: Boolean): Array<ProblemDescriptor> {
val message = getMessage(level)
isOnTheFly: Boolean,
fixes: Array<LocalQuickFix>): Array<ProblemDescriptor> {
val message = DevKitBundle.message("inspection.light.service.migration.message")
val holder = createProblemsHolder(aClass, manager, isOnTheFly)
holder.registerUProblem(aClass, message)
holder.registerUProblem(aClass, message, *fixes)
return holder.resultsArray
}
}
@@ -12,8 +12,6 @@ import com.intellij.psi.PsiClass
import com.intellij.util.xml.DomElement
import com.intellij.util.xml.DomUtil
import com.siyeh.ig.callMatcher.CallMatcher
import org.jetbrains.annotations.Nls
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.dom.Extension
import org.jetbrains.idea.devkit.util.DevKitDomUtil
import org.jetbrains.idea.devkit.util.PluginPlatformInfo
@@ -48,14 +46,6 @@ private fun hasDisallowedAttributes(extension: Extension): Boolean {
return false
}
@Nls(capitalization = Nls.Capitalization.Sentence)
internal fun getMessage(level: Service.Level): String {
return when (level) {
Service.Level.APP -> DevKitBundle.message("inspection.light.service.migration.app.level.message")
Service.Level.PROJECT -> DevKitBundle.message("inspection.light.service.migration.project.level.message")
}
}
internal fun isVersion193OrHigher(element: DomElement): Boolean {
if (PsiUtil.isIdeaProject(element.module?.project)) return true
val buildNumber = PluginPlatformInfo.forDomElement(element).sinceBuildNumber
@@ -16,6 +16,7 @@ import com.intellij.util.xml.highlighting.DomHighlightingHelper
import com.intellij.util.xml.highlighting.RemoveDomElementQuickFix
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.dom.Extension
import org.jetbrains.idea.devkit.inspections.quickfix.ConvertToLightServiceFix
import org.jetbrains.uast.UClass
import org.jetbrains.uast.toUElement
@@ -40,8 +41,9 @@ internal class LightServiceMigrationXMLInspection : DevKitPluginXmlInspectionBas
holder.createProblem(element, ProblemHighlightType.ERROR, message, null, RemoveDomElementQuickFix(element))
}
else {
val message = getMessage(level)
holder.createProblem(element, message)
val message = DevKitBundle.message("inspection.light.service.migration.message")
val fix = ConvertToLightServiceFix(aClass, element.xmlTag, level)
holder.createProblem(element, message, fix)
}
}
}
@@ -0,0 +1,3 @@
package com.example.demo;
final class <warning descr="Service can be converted to a light service">My<caret>Service</warning> {}
@@ -0,0 +1,6 @@
package com.example.demo;
import com.intellij.openapi.components.Service;
@Service
final class MyService {}
@@ -0,0 +1,3 @@
package com.example.demo;
final class <warning descr="Service can be converted to a light service">My<caret>Service</warning> {}
@@ -0,0 +1,6 @@
package com.example.demo;
import com.intellij.openapi.components.Service;
@Service(Service.Level.PROJECT)
final class MyService {}
@@ -1,4 +0,0 @@
package com.example.demo;
final class <warning descr="Service can be converted to a light one.
Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file.">MyService</warning> {}
@@ -1,4 +0,0 @@
package com.example.demo;
final class <warning descr="Service can be converted to a light one.
Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file.">MyService</warning> {}
@@ -0,0 +1,3 @@
package com.example.demo;
final class MyService {}
@@ -5,7 +5,6 @@
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light one.
Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file.">applicationService</warning> serviceImplementation="com.example.demo.MyService"/>
<applicationService serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -5,7 +5,6 @@
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light one.
Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file.">projectService</warning> serviceImplementation="com.example.demo.MyService"/>
<projectService serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -5,7 +5,6 @@
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light one.
Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file.">applicationService</warning> serviceImplementation="com.example.demo.MyService"/>
<<warning descr="Service can be converted to a light service">application<caret>Service</warning> serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij"/>
</idea-plugin>
@@ -0,0 +1,10 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light service">project<caret>Service</warning> serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij"/>
</idea-plugin>
@@ -9,16 +9,29 @@ internal class LightServiceMigrationInspectionTest : LightServiceMigrationInspec
private val CANNOT_BE_LIGHT_SERVICE_XML = "cannotBeLightService.xml"
private val CANNOT_BE_LIGHT_SERVICE_JAVA = "CannotBeLightService.java"
private val MY_SERVICE_JAVA = "MyService.java"
override fun getBasePath(): String = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/lightServiceMigration"
override fun getFileExtension(): String = "java"
fun testCanBeLightServiceAppLevel() {
doTest(getCodeFilePath(), getXmlFilePath())
fun testAddAppServiceAnnotation() {
myFixture.copyFileToProject(getXmlFilePath())
doTest(convertToLightServiceAppLevel, fileExtension, getTestName(false))
}
fun testCanBeLightServiceProjectLevel() {
doTest(getCodeFilePath(), getXmlFilePath())
fun testAddProjectServiceAnnotation() {
myFixture.copyFileToProject(getXmlFilePath())
doTest(convertToLightServiceProjectLevel, fileExtension, getTestName(false))
}
fun testRemoveAppServiceRegistration() {
myFixture.copyFileToProject(MY_SERVICE_JAVA)
doTest(convertToLightServiceAppLevel, xmlExtension, getTestName(true))
}
fun testRemoveProjectServiceRegistration() {
myFixture.copyFileToProject(MY_SERVICE_JAVA)
doTest(convertToLightServiceProjectLevel, xmlExtension, getTestName(true))
}
fun testNonFinalClass() {
@@ -39,11 +52,11 @@ internal class LightServiceMigrationInspectionTest : LightServiceMigrationInspec
fun testLightService() {
myFixture.copyFileToProject(getCodeFilePath())
DevKitInspectionFixTestBase.doTest(myFixture, "Remove <applicationService> element", "xml", getTestName(true))
doTest("Remove <applicationService> element", xmlExtension, getTestName(true))
}
fun testLibraryClass() {
myFixture.testHighlighting(getTestName(true) + ".xml")
myFixture.testHighlighting(getTestName(true) + "." + xmlExtension)
}
fun testUnitTestMode() {
@@ -0,0 +1,3 @@
package com.example.demo
class <warning descr="Service can be converted to a light service">My<caret>Service</warning>
@@ -0,0 +1,6 @@
package com.example.demo
import com.intellij.openapi.components.Service
@Service
class MyService
@@ -0,0 +1,3 @@
package com.example.demo
class <warning descr="Service can be converted to a light service">My<caret>Service</warning>
@@ -0,0 +1,6 @@
package com.example.demo
import com.intellij.openapi.components.Service
@Service(Service.Level.PROJECT)
class MyService
@@ -1,4 +0,0 @@
package com.example.demo
class <warning descr="Service can be converted to a light one.
Annotate the service class with '@Service' and remove its registration from the 'plugin.xml' file.">MyService</warning>
@@ -1,4 +0,0 @@
package com.example.demo
class <warning descr="Service can be converted to a light one.
Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file.">MyService</warning>
@@ -0,0 +1,3 @@
package com.example.demo
class MyService
@@ -0,0 +1,10 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<applicationService serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -5,7 +5,6 @@
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light one.
Annotate the service class with '@Service(Service.Level.PROJECT)' and remove its registration from the 'plugin.xml' file.">projectService</warning> serviceImplementation="com.example.demo.MyService"/>
<projectService serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,10 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light service">application<caret>Service</warning> serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.applicationService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij"/>
</idea-plugin>
@@ -0,0 +1,10 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<<warning descr="Service can be converted to a light service">project<caret>Service</warning> serviceImplementation="com.example.demo.MyService"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,8 @@
<idea-plugin>
<id>com.example.demo</id>
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.projectService" beanClass="com.intellij.openapi.components.ServiceDescriptor"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij"/>
</idea-plugin>
@@ -10,16 +10,29 @@ internal class KtLightServiceMigrationInspectionTest : LightServiceMigrationInsp
private val CANNOT_BE_LIGHT_SERVICE_XML = "cannotBeLightService.xml"
private val CANNOT_BE_LIGHT_SERVICE_KT = "CannotBeLightService.kt"
private val MY_SERVICE_KT = "MyService.kt"
override fun getBasePath(): String = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/lightServiceMigration"
override fun getFileExtension(): String = "kt"
fun testCanBeLightServiceAppLevel() {
doTest(getCodeFilePath(), getXmlFilePath())
fun testAddAppServiceAnnotation() {
myFixture.copyFileToProject(getXmlFilePath())
doTest(convertToLightServiceAppLevel, fileExtension, getTestName(false))
}
fun testCanBeLightServiceProjectLevel() {
doTest(getCodeFilePath(), getXmlFilePath())
fun testAddProjectServiceAnnotation() {
myFixture.copyFileToProject(getXmlFilePath())
doTest(convertToLightServiceProjectLevel, fileExtension, getTestName(false))
}
fun testRemoveAppServiceRegistration() {
myFixture.copyFileToProject(MY_SERVICE_KT)
doTest(convertToLightServiceAppLevel, xmlExtension, getTestName(true))
}
fun testRemoveProjectServiceRegistration() {
myFixture.copyFileToProject(MY_SERVICE_KT)
doTest(convertToLightServiceProjectLevel, xmlExtension, getTestName(true))
}
fun testOpenClass() {
@@ -1,11 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections.quickfix
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.LightServiceMigrationCodeInspection
import org.jetbrains.idea.devkit.inspections.LightServiceMigrationXMLInspection
abstract class LightServiceMigrationInspectionTestBase : LightDevKitInspectionFixTestBase() {
protected val convertToLightServiceAppLevel: String = DevKitBundle.message("inspection.light.service.migration.app.level.fix")
protected val convertToLightServiceProjectLevel: String = DevKitBundle.message("inspection.light.service.migration.project.level.fix")
protected val xmlExtension = "xml"
override fun setUp() {
super.setUp()
addClasses()
@@ -17,12 +22,21 @@ abstract class LightServiceMigrationInspectionTestBase : LightDevKitInspectionFi
myFixture.testHighlightingAllFiles(true, false, false, codeFilePath, xmlFilePath)
}
protected fun doTest(fixName: String, fileExtension: String, testName: String) {
val fileNameBefore = "$testName.$fileExtension"
val fileNameAfter = testName + "_after." + fileExtension
myFixture.testHighlighting(fileNameBefore)
val intention = myFixture.findSingleIntention(fixName)
myFixture.launchAction(intention)
myFixture.checkResultByFile(fileNameBefore, fileNameAfter, true)
}
protected fun getCodeFilePath(): String {
return getTestName(false) + "." + fileExtension
}
protected fun getXmlFilePath(): String {
return getTestName(true) + ".xml"
return getTestName(true) + "." + xmlExtension
}
private fun addClasses() {