diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index c4238f3163f9..cb6e21fa078b 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -580,6 +580,9 @@ + @@ -654,6 +657,11 @@ + + + diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties index f50e82a61048..76af8ad6883a 100644 --- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties +++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties @@ -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 diff --git a/plugins/devkit/devkit-core/src/inspections/quickfix/ConvertToLightServiceFix.kt b/plugins/devkit/devkit-core/src/inspections/quickfix/ConvertToLightServiceFix.kt new file mode 100644 index 000000000000..b2cb35422f91 --- /dev/null +++ b/plugins/devkit/devkit-core/src/inspections/quickfix/ConvertToLightServiceFix.kt @@ -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, + private val xmlTagPointer: SmartPsiElementPointer, + 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 = + ExtensionPointName.create("DevKit.lang.addServiceAnnotationProvider") + +internal object AddServiceAnnotationProviders : LanguageExtension(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) + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml b/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml index e2ee2ad37174..4cb2aa2d0e66 100644 --- a/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml +++ b/plugins/devkit/intellij.kotlin.devkit/resources/intellij.kotlin.devkit.xml @@ -82,5 +82,8 @@ + diff --git a/plugins/devkit/intellij.kotlin.devkit/src/inspections/KotlinAddServiceAnnotationProvider.kt b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KotlinAddServiceAnnotationProvider.kt new file mode 100644 index 000000000000..57009d06d9b0 --- /dev/null +++ b/plugins/devkit/intellij.kotlin.devkit/src/inspections/KotlinAddServiceAnnotationProvider.kt @@ -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) + } +} \ No newline at end of file