KotlinElementActionsFactory.createAddAnnotationActions: adding @field annotation target for targetless annotations (KT-18459)

This commit is contained in:
Nicolay Mitropolsky
2019-03-12 20:31:33 +03:00
parent 3bf0fb8957
commit 69ea0175a2
4 changed files with 297 additions and 29 deletions
@@ -29,6 +29,7 @@ import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.psi.*
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl
import com.intellij.psi.util.PropertyUtil
import com.intellij.psi.util.PropertyUtilBase
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
@@ -36,14 +37,14 @@ import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.appendModifier
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
@@ -52,6 +53,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.CreateCallableFromUsageFix
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.load.java.components.TypeUsage
@@ -70,7 +72,9 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.KotlinType
@@ -449,11 +453,21 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
override fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> {
val declaration = (target as? KtLightElement<*, *>)?.kotlinOrigin as? KtModifierListOwner ?: return emptyList()
if (declaration.language != KotlinLanguage.INSTANCE) return emptyList()
return listOf(CreateAnnotationAction(declaration, request))
val annotationUseSiteTarget = when (target) {
is JvmField -> AnnotationUseSiteTarget.FIELD
is JvmMethod -> when {
PropertyUtil.isSimplePropertySetter(target as? PsiMethod) -> AnnotationUseSiteTarget.PROPERTY_SETTER
PropertyUtil.isSimplePropertyGetter(target as? PsiMethod) -> AnnotationUseSiteTarget.PROPERTY_GETTER
else -> null
}
else -> null
}
return listOf(CreateAnnotationAction(declaration, annotationUseSiteTarget, request))
}
private class CreateAnnotationAction(
target: KtModifierListOwner,
val annotationTarget: AnnotationUseSiteTarget?,
val request: AnnotationRequest
) : IntentionAction {
@@ -471,14 +485,30 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val target = pointer.element ?: return
val kotlinAnnotation = JavaPsiFacade.getInstance(project).findClass(
request.qualifiedName,
target.resolveScope
)?.language == KotlinLanguage.INSTANCE
val annotationClass = JavaPsiFacade.getInstance(project).findClass(request.qualifiedName, target.resolveScope)
val kotlinAnnotation = annotationClass?.language == KotlinLanguage.INSTANCE
val annotationUseSiteTargetPrefix = run prefixEvaluation@{
if (annotationTarget == null) return@prefixEvaluation ""
val moduleDescriptor = (target as? KtDeclaration)?.resolveToDescriptorIfAny()?.module ?: return@prefixEvaluation ""
val annotationClassDescriptor = moduleDescriptor.resolveClassByFqName(
FqName(request.qualifiedName), NoLookupLocation.FROM_IDE
) ?: return@prefixEvaluation ""
val applicableTargetSet =
AnnotationChecker.applicableTargetSet(annotationClassDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
if (KotlinTarget.PROPERTY !in applicableTargetSet) return@prefixEvaluation ""
"${annotationTarget.renderName}:"
}
val entry = target.addAnnotationEntry(
KtPsiFactory(target)
.createAnnotationEntry(
"@${request.qualifiedName}${
"@$annotationUseSiteTargetPrefix${request.qualifiedName}${
request.attributes.mapIndexed { i, p ->
if (!kotlinAnnotation && i == 0 && p.name == "value")
renderAttributeValue(p.value).toString()
@@ -20,10 +20,7 @@ import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.jvm.JvmClass
import com.intellij.lang.jvm.JvmElement
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.JvmModifiersOwner
import com.intellij.lang.jvm.*
import com.intellij.lang.jvm.actions.*
import com.intellij.lang.jvm.types.JvmType
import com.intellij.openapi.editor.Editor
@@ -32,6 +29,7 @@ import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.psi.*
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl
import com.intellij.psi.util.PropertyUtil
import com.intellij.psi.util.PropertyUtilBase
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
@@ -39,14 +37,14 @@ import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.SourceElement
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.appendModifier
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
@@ -55,6 +53,7 @@ import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.*
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.createCallable.CreateCallableFromUsageFix
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi.JVM_FIELD_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.load.java.components.TypeUsage
@@ -73,7 +72,9 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
import org.jetbrains.kotlin.resolve.AnnotationChecker
import org.jetbrains.kotlin.resolve.annotations.JVM_STATIC_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.KotlinType
@@ -452,11 +453,21 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
override fun createAddAnnotationActions(target: JvmModifiersOwner, request: AnnotationRequest): List<IntentionAction> {
val declaration = (target as? KtLightElement<*, *>)?.kotlinOrigin as? KtModifierListOwner ?: return emptyList()
if (declaration.language != KotlinLanguage.INSTANCE) return emptyList()
return listOf(CreateAnnotationAction(declaration, request))
val annotationUseSiteTarget = when (target) {
is JvmField -> AnnotationUseSiteTarget.FIELD
is JvmMethod -> when {
PropertyUtil.isSimplePropertySetter(target as? PsiMethod) -> AnnotationUseSiteTarget.PROPERTY_SETTER
PropertyUtil.isSimplePropertyGetter(target as? PsiMethod) -> AnnotationUseSiteTarget.PROPERTY_GETTER
else -> null
}
else -> null
}
return listOf(CreateAnnotationAction(declaration, annotationUseSiteTarget, request))
}
private class CreateAnnotationAction(
target: KtModifierListOwner,
val annotationTarget: AnnotationUseSiteTarget?,
val request: AnnotationRequest
) : IntentionAction {
@@ -474,14 +485,29 @@ class KotlinElementActionsFactory : JvmElementActionsFactory() {
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val target = pointer.element ?: return
val kotlinAnnotation = JavaPsiFacade.getInstance(project).findClass(
request.qualifiedName,
target.resolveScope
)?.language == KotlinLanguage.INSTANCE
val annotationClass = JavaPsiFacade.getInstance(project).findClass(request.qualifiedName, target.resolveScope)
val kotlinAnnotation = annotationClass?.language == KotlinLanguage.INSTANCE
val annotationUseSiteTargetPrefix = run prefixEvaluation@{
if (annotationTarget == null) return@prefixEvaluation ""
val moduleDescriptor = (target as? KtDeclaration)?.resolveToDescriptorIfAny()?.module ?: return@prefixEvaluation ""
val annotationClassDescriptor = moduleDescriptor.resolveClassByFqName(
FqName(request.qualifiedName), NoLookupLocation.FROM_IDE
) ?: return@prefixEvaluation ""
val applicableTargetSet = AnnotationChecker.applicableTargetSet(annotationClassDescriptor) ?: KotlinTarget.DEFAULT_TARGET_SET
if (KotlinTarget.PROPERTY !in applicableTargetSet) return@prefixEvaluation ""
"${annotationTarget.renderName}:"
}
val entry = target.addAnnotationEntry(
KtPsiFactory(target)
.createAnnotationEntry(
"@${request.qualifiedName}${
"@$annotationUseSiteTargetPrefix${request.qualifiedName}${
request.attributes.mapIndexed { i, p ->
if (!kotlinAnnotation && i == 0 && p.name == "value")
renderAttributeValue(p.value).toString()
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.idea.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.jvm.JvmClass
import com.intellij.lang.jvm.JvmElement
import com.intellij.lang.jvm.JvmMethod
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.*
import com.intellij.lang.jvm.types.JvmSubstitutor
@@ -19,6 +18,7 @@ import com.intellij.psi.*
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -240,6 +240,113 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
)
}
fun testAddJavaAnnotationOnFieldWithoutTarget() {
myFixture.addFileToProject(
"pkg/myannotation/JavaAnnotation.java", """
package pkg.myannotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//no @Target
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaAnnotation {
String value();
int param() default 0;
}
""".trimIndent()
)
myFixture.configureByText(
"foo.kt", """class Foo {
| val bar: String = null
|}""".trim().trimMargin()
)
myFixture.launchAction(
createAddAnnotationActions(
myFixture.findElementByText("bar", KtModifierListOwner::class.java).toLightElements().single { it is PsiField } as PsiField,
annotationRequest("pkg.myannotation.JavaAnnotation")
).single()
)
myFixture.checkResult(
"""
import pkg.myannotation.JavaAnnotation
class Foo {
@field:JavaAnnotation()
val bar: String = null
}
""".trimIndent(), true
)
TestCase.assertEquals(
"KtLightMethodImpl -> org.jetbrains.annotations.NotNull," +
" KtLightFieldForDeclaration -> pkg.myannotation.JavaAnnotation, org.jetbrains.annotations.NotNull",
annotationsString(myFixture.findElementByText("bar", KtModifierListOwner::class.java))
)
}
fun testAddJavaAnnotationOnField() {
myFixture.addFileToProject(
"pkg/myannotation/JavaAnnotation.java", """
package pkg.myannotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaAnnotation {
String value();
int param() default 0;
}
""".trimIndent()
)
myFixture.configureByText(
"foo.kt", """class Foo {
| val bar: String = null
|}""".trim().trimMargin()
)
myFixture.launchAction(
createAddAnnotationActions(
myFixture.findElementByText("bar", KtModifierListOwner::class.java).toLightElements().single { it is PsiField } as PsiField,
annotationRequest("pkg.myannotation.JavaAnnotation")
).single()
)
myFixture.checkResult(
"""
import pkg.myannotation.JavaAnnotation
class Foo {
@JavaAnnotation()
val bar: String = null
}
""".trimIndent(), true
)
TestCase.assertEquals(
"KtLightMethodImpl -> org.jetbrains.annotations.NotNull," +
" KtLightFieldForDeclaration -> pkg.myannotation.JavaAnnotation, org.jetbrains.annotations.NotNull",
annotationsString(myFixture.findElementByText("bar", KtModifierListOwner::class.java))
)
}
private fun annotationsString(findElementByText: KtModifierListOwner) = findElementByText.toLightElements()
.joinToString { elem -> "${elem.javaClass.simpleName} -> ${(elem as PsiModifierListOwner).annotations.joinToString { it.qualifiedName!! }}" }
fun testDontMakePublicPublic() {
myFixture.configureByText(
"foo.kt", """class Foo {
@@ -13,13 +13,11 @@ import com.intellij.lang.jvm.actions.*
import com.intellij.lang.jvm.types.JvmSubstitutor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair.pair
import com.intellij.psi.PsiJvmSubstitutor
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiSubstitutor
import com.intellij.psi.PsiType
import com.intellij.psi.*
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.toLightElements
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -238,6 +236,113 @@ class CommonIntentionActionsTest : LightPlatformCodeInsightFixtureTestCase() {
)
}
fun testAddJavaAnnotationOnFieldWithoutTarget() {
myFixture.addFileToProject(
"pkg/myannotation/JavaAnnotation.java", """
package pkg.myannotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//no @Target
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaAnnotation {
String value();
int param() default 0;
}
""".trimIndent()
)
myFixture.configureByText(
"foo.kt", """class Foo {
| val bar: String = null
|}""".trim().trimMargin()
)
myFixture.launchAction(
createAddAnnotationActions(
myFixture.findElementByText("bar", KtModifierListOwner::class.java).toLightElements().single { it is PsiField } as PsiField,
annotationRequest("pkg.myannotation.JavaAnnotation")
).single()
)
myFixture.checkResult(
"""
import pkg.myannotation.JavaAnnotation
class Foo {
@field:JavaAnnotation()
val bar: String = null
}
""".trimIndent(), true
)
TestCase.assertEquals(
"KtLightMethodImpl -> org.jetbrains.annotations.NotNull," +
" KtLightFieldForDeclaration -> pkg.myannotation.JavaAnnotation, org.jetbrains.annotations.NotNull",
annotationsString(myFixture.findElementByText("bar", KtModifierListOwner::class.java))
)
}
fun testAddJavaAnnotationOnField() {
myFixture.addFileToProject(
"pkg/myannotation/JavaAnnotation.java", """
package pkg.myannotation
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaAnnotation {
String value();
int param() default 0;
}
""".trimIndent()
)
myFixture.configureByText(
"foo.kt", """class Foo {
| val bar: String = null
|}""".trim().trimMargin()
)
myFixture.launchAction(
createAddAnnotationActions(
myFixture.findElementByText("bar", KtModifierListOwner::class.java).toLightElements().single { it is PsiField } as PsiField,
annotationRequest("pkg.myannotation.JavaAnnotation")
).single()
)
myFixture.checkResult(
"""
import pkg.myannotation.JavaAnnotation
class Foo {
@JavaAnnotation()
val bar: String = null
}
""".trimIndent(), true
)
TestCase.assertEquals(
"KtLightMethodImpl -> org.jetbrains.annotations.NotNull," +
" KtLightFieldForDeclaration -> pkg.myannotation.JavaAnnotation, org.jetbrains.annotations.NotNull",
annotationsString(myFixture.findElementByText("bar", KtModifierListOwner::class.java))
)
}
private fun annotationsString(findElementByText: KtModifierListOwner) = findElementByText.toLightElements()
.joinToString { elem -> "${elem.javaClass.simpleName} -> ${(elem as PsiModifierListOwner).annotations.joinToString { it.qualifiedName!! }}" }
fun testDontMakePublicPublic() {
myFixture.configureByText(
"foo.kt", """class Foo {