mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[jvm + devKit] Add action to change annotation attribute and use it in MismatchedLightServiceLevelAndCtorInspection
IJ-CR-102194 GitOrigin-RevId: bdef6309bfcaf710db8bdcc773ac43757856e8f1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
adf527e2e0
commit
8e56c5dcf2
+5
-4
@@ -2,10 +2,7 @@
|
||||
package com.intellij.lang.jvm.actions
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.lang.jvm.JvmClass
|
||||
import com.intellij.lang.jvm.JvmMethod
|
||||
import com.intellij.lang.jvm.JvmModifiersOwner
|
||||
import com.intellij.lang.jvm.JvmParameter
|
||||
import com.intellij.lang.jvm.*
|
||||
|
||||
/**
|
||||
* This extension point provides language-abstracted code modifications for JVM-based languages.
|
||||
@@ -24,6 +21,10 @@ abstract class JvmElementActionsFactory {
|
||||
|
||||
open fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
open fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
open fun createAddFieldActions(targetClass: JvmClass, request: CreateFieldRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
open fun createAddMethodActions(targetClass: JvmClass, request: CreateMethodRequest): List<IntentionAction> = emptyList()
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
package com.intellij.lang.jvm.actions
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.lang.jvm.JvmClass
|
||||
import com.intellij.lang.jvm.JvmMethod
|
||||
import com.intellij.lang.jvm.JvmModifiersOwner
|
||||
import com.intellij.lang.jvm.JvmParameter
|
||||
import com.intellij.lang.jvm.*
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
|
||||
val EP_NAME: ExtensionPointName<JvmElementActionsFactory> = ExtensionPointName.create(
|
||||
@@ -38,6 +35,14 @@ fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationReq
|
||||
}
|
||||
}
|
||||
|
||||
fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> {
|
||||
return createActions {
|
||||
it.createChangeAnnotationAttributeActions(annotation, attributeIndex, request)
|
||||
}
|
||||
}
|
||||
|
||||
fun createModifierActions(target: JvmModifiersOwner, request: ChangeModifierRequest): List<IntentionAction> {
|
||||
return createActions {
|
||||
it.createChangeModifierActions(target, request)
|
||||
|
||||
+5
-1
@@ -35,6 +35,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL;
|
||||
import static com.intellij.codeInsight.AnnotationUtil.CHECK_TYPE;
|
||||
|
||||
public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement implements LocalQuickFix {
|
||||
protected final String myAnnotation;
|
||||
final String[] myAnnotationsToRemove;
|
||||
@@ -166,7 +169,8 @@ public class AddAnnotationPsiFix extends LocalQuickFixOnPsiElement implements Lo
|
||||
PsiModifierList modifierList = modifierListOwner.getModifierList();
|
||||
return modifierList != null
|
||||
&& !(modifierList instanceof LightElement)
|
||||
&& !(modifierListOwner instanceof LightElement);
|
||||
&& !(modifierListOwner instanceof LightElement)
|
||||
&& !AnnotationUtil.isAnnotated(modifierListOwner, annotationFQN, CHECK_EXTERNAL | CHECK_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -448,4 +448,7 @@ qualify.method.call.fix=Qualify the call with ''{0}''
|
||||
qualify.method.call.family=Qualify method call
|
||||
|
||||
remove.redundant.nested.patterns.fix.text=Remove redundant nested pattern{0, choice, 1#|2#s}
|
||||
add.missing.nested.patterns.fix.text=Add missing nested pattern{0, choice, 1#|2#s}
|
||||
add.missing.nested.patterns.fix.text=Add missing nested pattern{0, choice, 1#|2#s}
|
||||
|
||||
change.annotation.attribute.value.family=Change annotation attribute
|
||||
change.annotation.attribute.value.text=Change ''{0}'' annotation attribute
|
||||
+8
@@ -85,6 +85,14 @@ class JavaElementActionsFactory : JvmElementActionsFactory() {
|
||||
return listOf(CreateAnnotationAction(declaration, request))
|
||||
}
|
||||
|
||||
override fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> {
|
||||
val psiAnnotation = annotation as? PsiAnnotation ?: return emptyList()
|
||||
if (psiAnnotation.language != JavaLanguage.INSTANCE) return emptyList()
|
||||
return listOf(ChangeAnnotationAttributeAction(psiAnnotation, request))
|
||||
}
|
||||
|
||||
override fun createAddFieldActions(targetClass: JvmClass, request: CreateFieldRequest): List<IntentionAction> {
|
||||
val javaClass = targetClass.toJavaClassOrNull() ?: return emptyList()
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.lang.java.actions
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
|
||||
import com.intellij.lang.jvm.actions.AnnotationAttributeRequest
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiAnnotation
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementFactory
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
|
||||
internal class ChangeAnnotationAttributeAction(annotation: PsiAnnotation, val request: AnnotationAttributeRequest) :
|
||||
LocalQuickFixAndIntentionActionOnPsiElement(annotation) {
|
||||
|
||||
override fun startInWriteAction(): Boolean = true
|
||||
|
||||
override fun getFamilyName(): String = QuickFixBundle.message("change.annotation.attribute.value.family")
|
||||
|
||||
override fun getText(): String = QuickFixBundle.message("change.annotation.attribute.value.text", request.name)
|
||||
|
||||
override fun generatePreview(project: Project, editor: Editor, file: PsiFile): IntentionPreviewInfo {
|
||||
val copy = PsiTreeUtil.findSameElementInCopy(startElement as PsiAnnotation, file)
|
||||
invokeImpl(copy, project)
|
||||
return IntentionPreviewInfo.DIFF
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) {
|
||||
invokeImpl(startElement as PsiAnnotation, project)
|
||||
}
|
||||
|
||||
private fun invokeImpl(annotation: PsiAnnotation, project: Project) {
|
||||
val factory = PsiElementFactory.getInstance(project)
|
||||
val value = CreateAnnotationAction.attributeRequestToValue(request.value, factory, null)
|
||||
annotation.setDeclaredAttributeValue(request.name, value)
|
||||
}
|
||||
}
|
||||
@@ -69,15 +69,14 @@ internal class CreateAnnotationAction(target: PsiModifierListOwner, override val
|
||||
psiElementFactory: PsiElementFactory,
|
||||
context: PsiElement?) {
|
||||
for ((name, value) in annotationRequest.attributes) {
|
||||
val memberValue = attributeRequestToValue(value, psiElementFactory, context, annotationRequest)
|
||||
annotation.setDeclaredAttributeValue(name.takeIf { name != "value" }, memberValue)
|
||||
val memberValue = attributeRequestToValue(value, psiElementFactory, context)
|
||||
annotation.setDeclaredAttributeValue(name.takeIf { name != PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME }, memberValue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun attributeRequestToValue(value: AnnotationAttributeValueRequest,
|
||||
psiElementFactory: PsiElementFactory,
|
||||
context: PsiElement?,
|
||||
annotationRequest: AnnotationRequest): PsiAnnotationMemberValue? = when (value) {
|
||||
internal fun attributeRequestToValue(value: AnnotationAttributeValueRequest,
|
||||
psiElementFactory: PsiElementFactory,
|
||||
context: PsiElement?): PsiAnnotationMemberValue? = when (value) {
|
||||
is AnnotationAttributeValueRequest.PrimitiveValue -> psiElementFactory
|
||||
.createExpressionFromText(value.value.toString(), null)
|
||||
is AnnotationAttributeValueRequest.StringValue -> psiElementFactory
|
||||
@@ -92,7 +91,7 @@ internal class CreateAnnotationAction(target: PsiModifierListOwner, override val
|
||||
}
|
||||
is AnnotationAttributeValueRequest.ArrayValue -> {
|
||||
val arrayExpressionText = value.members.joinToString {
|
||||
attributeRequestToValue(it, psiElementFactory, context, annotationRequest)?.text ?: ""
|
||||
attributeRequestToValue(it, psiElementFactory, context)?.text ?: ""
|
||||
}
|
||||
val dummyAnnotation = psiElementFactory.createAnnotationFromText("@dummy({$arrayExpressionText})", context)
|
||||
dummyAnnotation.findAttributeValue(null)
|
||||
|
||||
+11
-7
@@ -9,6 +9,7 @@ import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
|
||||
import com.intellij.lang.jvm.annotation.JvmAnnotationEnumFieldValue
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiAnnotation
|
||||
import com.intellij.psi.PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME
|
||||
import com.intellij.psi.PsiElementFactory
|
||||
import com.intellij.psi.PsiFile
|
||||
@@ -27,17 +28,20 @@ internal class MismatchedLightServiceLevelAndCtorInspection : DevKitJvmInspectio
|
||||
if (!method.isConstructor) return true
|
||||
val file: PsiFile = method.sourceElement?.containingFile ?: return true
|
||||
val containingClass = method.containingClass ?: return true
|
||||
val serviceAnnotation = containingClass.annotations.find { it.qualifiedName == Service::class.java.canonicalName } ?: return true
|
||||
val level = getLevel(serviceAnnotation)
|
||||
if (level !in listOf(Level.PROJECT, Level.APP_AND_PROJECT)) {
|
||||
val annotation = containingClass.annotations.find { it.qualifiedName == Service::class.java.canonicalName } ?: return true
|
||||
val annotationName = (annotation as? PsiAnnotation)?.nameReferenceElement
|
||||
val level = getLevel(annotation)
|
||||
if (annotationName != null && level !in listOf(Level.PROJECT, Level.APP_AND_PROJECT)) {
|
||||
val isProjectParamCtor = (method.parameters.singleOrNull()?.type as? PsiType)?.canonicalText == Project::class.java.canonicalName
|
||||
if (isProjectParamCtor) {
|
||||
val projectLevelFqn = "${Service.Level::class.java.canonicalName}.${Service.Level.PROJECT}"
|
||||
val request = annotationRequest(Service::class.java.canonicalName,
|
||||
constantAttribute(DEFAULT_REFERENCED_METHOD_NAME, projectLevelFqn))
|
||||
val actions = createAddAnnotationActions(containingClass, request)
|
||||
val request = constantAttribute(DEFAULT_REFERENCED_METHOD_NAME, projectLevelFqn)
|
||||
val actions = createChangeAnnotationAttributeActions(annotation, 0, request)
|
||||
val fixes = IntentionWrapper.wrapToQuickFixes(actions.toTypedArray(), file)
|
||||
sink.highlight(DevKitBundle.message("inspection.mismatched.light.service.level.and.ctor.project.level.required"), *fixes)
|
||||
val holder = (sink as HighlightSinkImpl).holder
|
||||
holder.registerProblem(annotationName,
|
||||
DevKitBundle.message("inspection.mismatched.light.service.level.and.ctor.project.level.required"),
|
||||
*fixes)
|
||||
}
|
||||
}
|
||||
if (level == Level.APP || level == Level.APP_AND_PROJECT) {
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.project.Project;
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@<warning descr="If constructor takes Project, Service.Level.PROJECT is required">Service</warning>(Service.Level.APP)
|
||||
final class MyService {
|
||||
private <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine"><warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning></warning>(Project project) {}
|
||||
private <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine">MyService<caret></warning>(Project project) {}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.project.Project;
|
||||
|
||||
@Service
|
||||
@<warning descr="If constructor takes Project, Service.Level.PROJECT is required">Service<caret></warning>
|
||||
final class MyService {
|
||||
private final Project myProject;
|
||||
|
||||
public <warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning>(Project project) {
|
||||
public MyService(Project project) {
|
||||
myProject = project;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.project.Project;
|
||||
|
||||
@Service({})
|
||||
@<warning descr="If constructor takes Project, Service.Level.PROJECT is required">Service<caret></warning>({})
|
||||
final class MyService {
|
||||
private final Project myProject;
|
||||
|
||||
public <warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning>(Project project) {
|
||||
public MyService(Project project) {
|
||||
myProject = project;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import com.intellij.openapi.components.Service;
|
||||
import com.intellij.openapi.project.Project;
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@<warning descr="If constructor takes Project, Service.Level.PROJECT is required">Service</warning>(Service.Level.APP)
|
||||
final class MyService {
|
||||
private <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine"><warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning></warning>(Project project) {}
|
||||
private <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine">MyService<caret></warning>(Project project) {}
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,6 +1,7 @@
|
||||
// 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
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
|
||||
import org.jetbrains.idea.devkit.inspections.quickfix.MismatchedLightServiceLevelAndCtorInspectionTestBase
|
||||
@@ -8,25 +9,26 @@ import org.jetbrains.idea.devkit.inspections.quickfix.MismatchedLightServiceLeve
|
||||
@TestDataPath("\$CONTENT_ROOT/testData/inspections/mismatchedLightServiceLevelAndCtor")
|
||||
class MismatchedLightServiceLevelAndCtorInspectionTest : MismatchedLightServiceLevelAndCtorInspectionTestBase() {
|
||||
|
||||
private val ANNOTATE_AS_SERVICE_FIX_NAME = "Annotate class 'MyService' as '@Service'"
|
||||
private val NO_ARG_CTOR_FIX_NAME = QuickFixBundle.message("change.method.parameters.text", "()")
|
||||
private val COROUTINE_SCOPE_PARAM_CTOR_FIX_NAME = QuickFixBundle.message("change.method.parameters.text", "(CoroutineScope scope)")
|
||||
|
||||
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/mismatchedLightServiceLevelAndCtor/"
|
||||
|
||||
override fun getFileExtension() = "java"
|
||||
|
||||
fun testMakeProjectLevel1() {
|
||||
doTest(ANNOTATE_AS_SERVICE_FIX_NAME)
|
||||
doTest(annotateAsServiceFixName)
|
||||
}
|
||||
|
||||
fun testMakeProjectLevel2() {
|
||||
doTest(ANNOTATE_AS_SERVICE_FIX_NAME)
|
||||
doTest(annotateAsServiceFixName)
|
||||
}
|
||||
|
||||
fun testRemoveProjectParam() {
|
||||
doTest("Change method parameters to '()'")
|
||||
doTest(NO_ARG_CTOR_FIX_NAME)
|
||||
}
|
||||
|
||||
fun testChangeParamToCoroutineScope() {
|
||||
doTest("Change method parameters to '(CoroutineScope scope)'")
|
||||
doTest(COROUTINE_SCOPE_PARAM_CTOR_FIX_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine"><warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning></warning>(val project: Project)
|
||||
<warning descr="If constructor takes Project, Service.Level.PROJECT is required">@Service(Service.Level.APP)</warning>
|
||||
class <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine">MyService<caret></warning>(val project: Project)
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
@Service
|
||||
class <warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning>(val project: Project)
|
||||
<warning descr="If constructor takes Project, Service.Level.PROJECT is required">@Service<caret></warning>
|
||||
class MyService(val project: Project)
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
@Service(*[])
|
||||
class <warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning>(val project: Project)
|
||||
<warning descr="If constructor takes Project, Service.Level.PROJECT is required">@Service<caret>(*[])</warning>
|
||||
class MyService(val project: Project)
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine"><warning descr="If constructor takes Project, Service.Level.PROJECT is required">MyService<caret></warning></warning>(val project: Project)
|
||||
<warning descr="If constructor takes Project, Service.Level.PROJECT is required">@Service(Service.Level.APP)</warning>
|
||||
class <warning descr="Application level service requires no-arg constructor or constructor taking Coroutine">MyService<caret></warning>(val project: Project)
|
||||
+9
-4
@@ -1,7 +1,10 @@
|
||||
// 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.codeInsight.daemon.QuickFixBundle
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jetbrains.idea.devkit.inspections.quickfix.MismatchedLightServiceLevelAndCtorInspectionTestBase
|
||||
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
|
||||
|
||||
@@ -13,18 +16,20 @@ class KtMismatchedLightServiceLevelAndCtorInspectionTest : MismatchedLightServic
|
||||
override fun getFileExtension() = "kt"
|
||||
|
||||
fun testMakeProjectLevel1() {
|
||||
doTest("Annotate as @Service")
|
||||
doTest(annotateAsServiceFixName)
|
||||
}
|
||||
|
||||
fun testMakeProjectLevel2() {
|
||||
doTest("Annotate as @Service")
|
||||
doTest(annotateAsServiceFixName)
|
||||
}
|
||||
|
||||
fun testRemoveProjectParam() {
|
||||
doTest("Remove 1st parameter from constructor 'MyService'")
|
||||
doTest(QuickFixBundle.message("remove.parameter.from.usage.text", 1, "parameter", "constructor", "MyService"))
|
||||
}
|
||||
|
||||
fun testChangeParamToCoroutineScope() {
|
||||
doTest("Change 1st parameter of constructor 'MyService' from 'Project' to 'CoroutineScope'")
|
||||
doTest(
|
||||
QuickFixBundle.message("change.parameter.from.usage.text", 1, "parameter", "constructor", "MyService", Project::class.java.simpleName,
|
||||
CoroutineScope::class.java.simpleName))
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -1,10 +1,15 @@
|
||||
// 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.daemon.QuickFixBundle
|
||||
import com.intellij.psi.PsiAnnotation
|
||||
import org.jetbrains.idea.devkit.inspections.MismatchedLightServiceLevelAndCtorInspection
|
||||
|
||||
abstract class MismatchedLightServiceLevelAndCtorInspectionTestBase : LightDevKitInspectionFixTestBase() {
|
||||
|
||||
protected val annotateAsServiceFixName = QuickFixBundle.message("change.annotation.attribute.value.text",
|
||||
PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(MismatchedLightServiceLevelAndCtorInspection())
|
||||
|
||||
+78
-6
@@ -21,6 +21,7 @@ import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
||||
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
@@ -40,7 +41,6 @@ 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
|
||||
@@ -416,6 +416,82 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun createChangeAnnotationAttributeActions(annotation: JvmAnnotation,
|
||||
attributeIndex: Int,
|
||||
request: AnnotationAttributeRequest): List<IntentionAction> {
|
||||
val annotationEntry = annotation.safeAs<KtLightElement<*, *>>()?.kotlinOrigin.safeAs<KtAnnotationEntry>().takeIf {
|
||||
it?.language == KotlinLanguage.INSTANCE
|
||||
} ?: return emptyList()
|
||||
return listOf(ChangeAnnotationAction(annotationEntry, attributeIndex, request))
|
||||
}
|
||||
|
||||
private class ChangeAnnotationAction(annotationEntry: KtAnnotationEntry,
|
||||
private val attributeIndex: Int,
|
||||
private val request: AnnotationAttributeRequest) : IntentionAction {
|
||||
|
||||
private val pointer: SmartPsiElementPointer<KtAnnotationEntry>
|
||||
private val qualifiedName: String
|
||||
|
||||
override fun startInWriteAction(): Boolean = true
|
||||
|
||||
override fun getFamilyName(): String = QuickFixBundle.message("change.annotation.attribute.value.family")
|
||||
|
||||
override fun getText(): String = QuickFixBundle.message("change.annotation.attribute.value.text", request.name)
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean = pointer.element != null
|
||||
|
||||
override fun generatePreview(project: Project, editor: Editor, file: PsiFile): IntentionPreviewInfo {
|
||||
invokeImpl(PsiTreeUtil.findSameElementInCopy(pointer.element, file), project)
|
||||
return IntentionPreviewInfo.DIFF
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: PsiFile) {
|
||||
val annotationEntry = pointer.element ?: return
|
||||
invokeImpl(annotationEntry, project)
|
||||
}
|
||||
|
||||
private fun invokeImpl(annotationEntry: KtAnnotationEntry, project: Project) {
|
||||
val facade = JavaPsiFacade.getInstance(annotationEntry.project)
|
||||
val isKotlinAnnotation = facade.findClass(qualifiedName, annotationEntry.resolveScope)?.language == KotlinLanguage.INSTANCE
|
||||
val dummyAnnotationRequest = annotationRequest(qualifiedName, request)
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
val annotationText = '@' + renderAnnotation(dummyAnnotationRequest, psiFactory) { isKotlinAnnotation }
|
||||
val dummyArgumentList = psiFactory.createAnnotationEntry(annotationText).valueArgumentList!!
|
||||
val argumentList = annotationEntry.valueArgumentList
|
||||
if (argumentList == null) {
|
||||
annotationEntry.add(dummyArgumentList)
|
||||
}
|
||||
else {
|
||||
val dummyArgument = dummyArgumentList.arguments[0]
|
||||
val attribute = findAttribute(annotationEntry, request.name, attributeIndex)
|
||||
if (attribute != null) {
|
||||
argumentList.addArgumentBefore(dummyArgument, attribute.value)
|
||||
argumentList.removeArgument(attribute.index + 1)
|
||||
}
|
||||
else {
|
||||
argumentList.addArgument(dummyArgument)
|
||||
}
|
||||
}
|
||||
ShortenReferences.DEFAULT.process(annotationEntry)
|
||||
}
|
||||
|
||||
private fun findAttribute(annotationEntry: KtAnnotationEntry, name: String, index: Int): IndexedValue<KtValueArgument>? {
|
||||
val arguments = annotationEntry.valueArgumentList?.arguments ?: return null
|
||||
arguments.withIndex().find { (_, argument) ->
|
||||
argument.getArgumentName()?.asName?.identifier == name
|
||||
}?.let {
|
||||
return it
|
||||
}
|
||||
val valueArgument = arguments.getOrNull(index) ?: return null
|
||||
return IndexedValue(index, valueArgument)
|
||||
}
|
||||
|
||||
init {
|
||||
pointer = annotationEntry.createSmartPointer()
|
||||
qualifiedName = annotationEntry.toLightAnnotation()?.qualifiedName ?: throw IllegalStateException("r")
|
||||
}
|
||||
}
|
||||
|
||||
override fun createChangeParametersActions(target: JvmMethod, request: ChangeParametersRequest): List<IntentionAction> {
|
||||
return when (val kotlinOrigin = (target as? KtLightElement<*, *>)?.kotlinOrigin) {
|
||||
is KtNamedFunction -> listOfNotNull(ChangeMethodParameters.create(kotlinOrigin, request))
|
||||
@@ -554,11 +630,7 @@ internal fun addAnnotationEntry(
|
||||
val psiFactory = KtPsiFactory(target.project)
|
||||
// could be generated via descriptor when KT-30478 is fixed
|
||||
val annotationText = '@' + annotationUseSiteTargetPrefix + renderAnnotation(target, request, psiFactory)
|
||||
val annotationEntry = psiFactory.createAnnotationEntry(annotationText)
|
||||
target.findAnnotation(FqName(request.qualifiedName))?.let {
|
||||
return it.replace(annotationEntry) as KtAnnotationEntry
|
||||
}
|
||||
return target.addAnnotationEntry(annotationEntry)
|
||||
return target.addAnnotationEntry(psiFactory.createAnnotationEntry(annotationText))
|
||||
}
|
||||
|
||||
private fun renderAnnotation(target: PsiElement, request: AnnotationRequest, psiFactory: KtPsiFactory): String {
|
||||
|
||||
Reference in New Issue
Block a user