[devkit] Introduce ConvertToLightServiceFix

^IDEA-254577

GitOrigin-RevId: 627bc05e748c753df7b98fd061007deca4a0380e
This commit is contained in:
Andrey Cherkasov
2023-12-13 20:03:26 +00:00
committed by intellij-monorepo-bot
parent 266ff1b037
commit f596a23d56
5 changed files with 134 additions and 0 deletions
@@ -580,6 +580,9 @@
<appServiceAsStaticFinalFieldOrPropertyFixProvider
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaAppServiceAsStaticFinalFieldOrPropertyFixProvider"/>
<addServiceAnnotationProvider
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaAddServiceAnnotationProvider"/>
</extensions>
<extensions defaultExtensionNs="JavaScript.JsonSchema">
@@ -654,6 +657,11 @@
<with attribute="implementationClass"
implements="org.jetbrains.idea.devkit.inspections.MethodNameProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="DevKit.lang.addServiceAnnotationProvider"
beanClass="com.intellij.lang.LanguageExtensionPoint"
dynamic="true">
<with attribute="implementationClass" implements="org.jetbrains.idea.devkit.inspections.quickfix.AddServiceAnnotationProvider"/>
</extensionPoint>
</extensionPoints>
<actions>
@@ -646,6 +646,9 @@ inspection.light.service.migration.app.level.message=Service can be converted to
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.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
inspection.light.service.migration.already.annotated.message=Service class annotated with '@Service' must not be registered in the 'plugin.xml' file
inspection.action.presentation.instantiated.in.ctor.display.name=Eager creation of action presentation
@@ -0,0 +1,81 @@
// 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 com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.codeInsight.intention.AddAnnotationFix
import com.intellij.codeInsight.intention.AddAnnotationPsiFix
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.components.Service
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.xml.XmlTag
import com.intellij.refactoring.suggested.createSmartPointer
import com.intellij.util.xml.highlighting.RemoveDomElementQuickFix
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.getProjectLevelFQN
internal class ConvertToLightServiceFix private constructor(private val classPointer: SmartPsiElementPointer<PsiElement>,
private val xmlTagPointer: SmartPsiElementPointer<XmlTag>,
private val level: Service.Level) : LocalQuickFix {
constructor(aClass: PsiElement, xmlTag: XmlTag, level: Service.Level) : this(aClass.createSmartPointer(),
xmlTag.createSmartPointer(),
level)
override fun getFamilyName(): String {
return DevKitBundle.message("inspection.light.service.migration.family.name")
}
override fun getName(): String {
val key = when (level) {
Service.Level.APP -> "inspection.light.service.migration.app.level.fix"
Service.Level.PROJECT -> "inspection.light.service.migration.project.level.fix"
}
return DevKitBundle.message(key)
}
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo {
return IntentionPreviewInfo.EMPTY
}
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val aClass = classPointer.element ?: return
val xmlTag = xmlTagPointer.element ?: return
val addServiceAnnotationProvider = AddServiceAnnotationProviders.forLanguage(aClass.language) ?: return
addServiceAnnotationProvider.addServiceAnnotation(aClass, level)
RemoveDomElementQuickFix.removeXmlTag(xmlTag, project)
}
}
private val EP_NAME: ExtensionPointName<AddServiceAnnotationProvider> =
ExtensionPointName.create("DevKit.lang.addServiceAnnotationProvider")
internal object AddServiceAnnotationProviders : LanguageExtension<AddServiceAnnotationProvider>(EP_NAME.name)
interface AddServiceAnnotationProvider {
fun addServiceAnnotation(aClass: PsiElement, level: Service.Level)
}
internal class JavaAddServiceAnnotationProvider : AddServiceAnnotationProvider {
override fun addServiceAnnotation(aClass: PsiElement, level: Service.Level) {
if (aClass !is PsiClass) return
val fix = when (level) {
Service.Level.APP -> AddAnnotationPsiFix(Service::class.java.canonicalName, aClass)
Service.Level.PROJECT -> {
val factory = JavaPsiFacade.getElementFactory(aClass.project)
val projectLevelFqn = getProjectLevelFQN()
val newAnnotation = factory.createAnnotationFromText("@${Service::class.java.canonicalName}(${projectLevelFqn})", aClass)
val attributes = newAnnotation.parameterList.attributes
AddAnnotationFix(Service::class.java.canonicalName, aClass, attributes, ExternalAnnotationsManager.AnnotationPlace.IN_CODE)
}
}
fix.invoke(aClass.project, aClass.containingFile, aClass, aClass)
}
}
@@ -82,5 +82,8 @@
<methodNameProvider
language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtMethodNameProvider"/>
<addServiceAnnotationProvider
language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KotlinAddServiceAnnotationProvider"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,39 @@
// 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.kotlin.inspections
import com.intellij.openapi.components.Service
import com.intellij.psi.PsiElement
import org.jetbrains.idea.devkit.inspections.getProjectLevelFQN
import org.jetbrains.idea.devkit.inspections.quickfix.AddServiceAnnotationProvider
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.idea.quickfix.AddAnnotationFix
import org.jetbrains.kotlin.idea.quickfix.AddAnnotationWithArgumentsFix
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClass
internal class KotlinAddServiceAnnotationProvider : AddServiceAnnotationProvider {
override fun addServiceAnnotation(aClass: PsiElement, level: Service.Level) {
val ktClass = when (aClass) {
is KtClass -> aClass
is KtLightClass -> aClass.kotlinOrigin
else -> return
}
val file = ktClass?.containingFile ?: return
val annotationFqName = FqName(Service::class.java.canonicalName)
val fix = when (level) {
Service.Level.APP -> {
val annotationClassId = ClassId.topLevel(annotationFqName)
AddAnnotationFix(ktClass, annotationClassId, AddAnnotationFix.Kind.Self)
}
Service.Level.PROJECT -> {
val kind = AddAnnotationWithArgumentsFix.Kind.Self
val projectLevelFqn = getProjectLevelFQN()
val arguments = listOf(projectLevelFqn)
AddAnnotationWithArgumentsFix(ktClass, annotationFqName, arguments, kind)
}
}
fix.invoke(file.project, null, file)
}
}