Correct 'asJava' view of annotations as annotation arguments
Fix light elements for nested arrays/annotations inside annotations #KT-18378 Fixed
This commit is contained in:
committed by
Pavel V. Talanov
parent
6eb534fca8
commit
a6b446cc4f
+116
-69
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.asJava.elements
|
package org.jetbrains.kotlin.asJava.elements
|
||||||
|
|
||||||
|
import com.intellij.lang.Language
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
@@ -24,19 +25,21 @@ import org.jetbrains.annotations.Nullable
|
|||||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||||
import org.jetbrains.kotlin.asJava.classes.cannotModify
|
import org.jetbrains.kotlin.asJava.classes.cannotModify
|
||||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
|
||||||
abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) :
|
abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) :
|
||||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtAnnotationEntry, PsiAnnotation> {
|
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||||
override val clsDelegate by lazyPub(computeDelegate)
|
override val clsDelegate by lazyPub(computeDelegate)
|
||||||
|
|
||||||
override fun getNameReferenceElement() = clsDelegate.nameReferenceElement
|
override fun getNameReferenceElement() = clsDelegate.nameReferenceElement
|
||||||
@@ -52,48 +55,19 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: ()
|
|||||||
|
|
||||||
class KtLightAnnotationForSourceEntry(
|
class KtLightAnnotationForSourceEntry(
|
||||||
private val qualifiedName: String,
|
private val qualifiedName: String,
|
||||||
override val kotlinOrigin: KtAnnotationEntry,
|
override val kotlinOrigin: KtCallElement,
|
||||||
parent: KtLightElement<*, *>,
|
parent: PsiElement,
|
||||||
computeDelegate: () -> PsiAnnotation
|
computeDelegate: () -> PsiAnnotation
|
||||||
) : KtLightAbstractAnnotation(parent, computeDelegate) {
|
) : KtLightAbstractAnnotation(parent, computeDelegate) {
|
||||||
|
|
||||||
override fun getQualifiedName() = qualifiedName
|
override fun getQualifiedName() = qualifiedName
|
||||||
|
|
||||||
open inner class LightExpressionValue<out D : PsiExpression>(
|
open inner class LightElementValue<out D : PsiElement>(
|
||||||
val delegate: D,
|
val delegate: D,
|
||||||
private val parent: PsiElement
|
private val parent: PsiElement,
|
||||||
) : PsiAnnotationMemberValue, PsiExpression by delegate {
|
private val originalExpressionProvider: () -> PsiElement
|
||||||
val originalExpression: PsiElement? by lazyPub {
|
) : PsiAnnotationMemberValue, PsiElement by delegate {
|
||||||
val nameAndValue = delegate.getStrictParentOfType<PsiNameValuePair>() ?: return@lazyPub null
|
val originalExpression: PsiElement by lazyPub { originalExpressionProvider() }
|
||||||
val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin
|
|
||||||
val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry)
|
|
||||||
val resolvedCall = annotationEntry.getResolvedCall(context) ?: return@lazyPub null
|
|
||||||
val annotationConstructor = resolvedCall.resultingDescriptor
|
|
||||||
val parameterName = nameAndValue.name ?: "value"
|
|
||||||
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName }
|
|
||||||
?: return@lazyPub null
|
|
||||||
val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return@lazyPub null
|
|
||||||
when (resolvedArgument) {
|
|
||||||
is DefaultValueArgument -> {
|
|
||||||
val psi = parameter.source.getPsi()
|
|
||||||
when (psi) {
|
|
||||||
is KtParameter -> psi.defaultValue
|
|
||||||
is PsiAnnotationMethod -> psi.defaultValue
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is ExpressionValueArgument -> {
|
|
||||||
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
|
||||||
// arrayOf()
|
|
||||||
if (argExpression is KtCallExpression) unwrapArray(argExpression.valueArguments) else argExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
is VarargValueArgument -> unwrapArray(resolvedArgument.arguments)
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getConstantValue(): Any? {
|
fun getConstantValue(): Any? {
|
||||||
val expression = originalExpression as? KtExpression ?: return null
|
val expression = originalExpression as? KtExpression ?: return null
|
||||||
@@ -102,23 +76,17 @@ class KtLightAnnotationForSourceEntry(
|
|||||||
return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE)
|
return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unwrapArray(arguments: List<ValueArgument>): PsiElement? {
|
|
||||||
val arrayInitializer = parent as? LightArrayInitializerValue ?: return null
|
|
||||||
val exprIndex = arrayInitializer.initializers.indexOf(this)
|
|
||||||
if (exprIndex < 0 || exprIndex >= arguments.size) return null
|
|
||||||
return arguments[exprIndex].getArgumentExpression()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getReference() = references.singleOrNull()
|
override fun getReference() = references.singleOrNull()
|
||||||
override fun getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY
|
override fun getReferences() = originalExpression.references
|
||||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
override fun getLanguage(): Language = KotlinLanguage.INSTANCE
|
||||||
override fun getNavigationElement() = originalExpression
|
override fun getNavigationElement() = originalExpression
|
||||||
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
override fun getTextRange() = originalExpression.textRange ?: TextRange.EMPTY_RANGE
|
||||||
override fun getParent() = parent
|
override fun getParent() = parent
|
||||||
|
override fun getText(): String = originalExpression.text
|
||||||
|
|
||||||
override fun replace(newElement: PsiElement): PsiElement {
|
override fun replace(newElement: PsiElement): PsiElement {
|
||||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||||
val origin = originalExpression ?: return this
|
val origin = originalExpression
|
||||||
|
|
||||||
val exprToReplace =
|
val exprToReplace =
|
||||||
if (origin is KtCallExpression /*arrayOf*/) {
|
if (origin is KtCallExpression /*arrayOf*/) {
|
||||||
@@ -133,39 +101,106 @@ class KtLightAnnotationForSourceEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement {
|
||||||
|
val resolvedCall = callHolder.getResolvedCall()!!
|
||||||
|
val annotationConstructor = resolvedCall.resultingDescriptor
|
||||||
|
val parameterName =
|
||||||
|
memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?:
|
||||||
|
memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.name ?:
|
||||||
|
"value"
|
||||||
|
|
||||||
|
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName }
|
||||||
|
?: error("single parameter '$parameterName' was not found in ${annotationConstructor.valueParameters.map { it.name.asString() }}")
|
||||||
|
val resolvedArgument = resolvedCall.valueArguments[parameter]!!
|
||||||
|
return when (resolvedArgument) {
|
||||||
|
is DefaultValueArgument -> {
|
||||||
|
val psi = parameter.source.getPsi()
|
||||||
|
when (psi) {
|
||||||
|
is KtParameter -> psi.defaultValue!!
|
||||||
|
is PsiAnnotationMethod -> psi.defaultValue!!
|
||||||
|
else -> throw UnsupportedOperationException("$psi of type ${psi?.javaClass}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ExpressionValueArgument -> {
|
||||||
|
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
||||||
|
argExpression?.asKtCall()
|
||||||
|
?: argExpression
|
||||||
|
?: error("resolvedArgument ($resolvedArgument) has no arg expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
is VarargValueArgument ->
|
||||||
|
memberValue.unwrapArray(resolvedArgument.arguments)
|
||||||
|
?: resolvedArgument.arguments.first().asElement().parent.parent.let {
|
||||||
|
it.asKtCall() ?: it
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("resolvedArgument: {$resolvedArgument} cant be processed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.unwrapArray(arguments: List<ValueArgument>): PsiElement? {
|
||||||
|
val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null
|
||||||
|
val exprIndex = arrayInitializer.initializers.indexOf(this)
|
||||||
|
if (exprIndex < 0 || exprIndex >= arguments.size) return null
|
||||||
|
return arguments[exprIndex].getArgumentExpression()
|
||||||
|
}
|
||||||
|
|
||||||
|
open inner class LightExpressionValue<out D : PsiExpression>(
|
||||||
|
delegate: D,
|
||||||
|
parent: PsiElement,
|
||||||
|
originalExpressionProvider: () -> PsiElement
|
||||||
|
) : LightElementValue<D>(delegate, parent, originalExpressionProvider), PsiExpression {
|
||||||
|
override fun getType(): PsiType? = delegate.type
|
||||||
|
}
|
||||||
|
|
||||||
inner class LightStringLiteral(
|
inner class LightStringLiteral(
|
||||||
delegate: PsiLiteralExpression,
|
delegate: PsiLiteralExpression,
|
||||||
parent: PsiElement
|
parent: PsiElement,
|
||||||
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent), PsiLiteralExpression {
|
originalExpressionProvider: () -> PsiElement
|
||||||
|
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, originalExpressionProvider), PsiLiteralExpression {
|
||||||
override fun getValue() = delegate.value
|
override fun getValue() = delegate.value
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LightClassLiteral(
|
inner class LightClassLiteral(
|
||||||
delegate: PsiClassObjectAccessExpression,
|
delegate: PsiClassObjectAccessExpression,
|
||||||
parent: PsiElement
|
parent: PsiElement,
|
||||||
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent), PsiClassObjectAccessExpression {
|
originalExpressionProvider: () -> PsiElement
|
||||||
|
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, originalExpressionProvider), PsiClassObjectAccessExpression {
|
||||||
override fun getType() = delegate.type
|
override fun getType() = delegate.type
|
||||||
override fun getOperand(): PsiTypeElement = delegate.operand
|
override fun getOperand(): PsiTypeElement = delegate.operand
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LightArrayInitializerValue(
|
inner class LightArrayInitializerValue(
|
||||||
private val delegate: PsiArrayInitializerMemberValue,
|
delegate: PsiArrayInitializerMemberValue,
|
||||||
private val parent: PsiElement
|
parent: PsiElement,
|
||||||
) : PsiArrayInitializerMemberValue by delegate {
|
originalExpressionProvider: () -> PsiElement
|
||||||
private val _initializers by lazyPub { delegate.initializers.map { wrapAnnotationValue(it, this) }.toTypedArray() }
|
) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, originalExpressionProvider), PsiArrayInitializerMemberValue {
|
||||||
|
private val _initializers by lazyPub {
|
||||||
|
delegate.initializers.mapIndexed { i, it ->
|
||||||
|
wrapAnnotationValue(it, this, {
|
||||||
|
(originalExpression as KtCallElement).valueArguments[i].getArgumentExpression()!!
|
||||||
|
})
|
||||||
|
}.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getInitializers() = _initializers
|
override fun getInitializers() = _initializers
|
||||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
|
||||||
override fun getParent() = parent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement): PsiAnnotationMemberValue {
|
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOriginForElement: () -> PsiElement): PsiAnnotationMemberValue {
|
||||||
return when {
|
return when {
|
||||||
value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent)
|
value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOriginForElement)
|
||||||
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent)
|
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOriginForElement)
|
||||||
value is PsiExpression -> LightExpressionValue(value, parent)
|
value is PsiExpression -> LightExpressionValue(value, parent, ktOriginForElement)
|
||||||
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent)
|
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOriginForElement)
|
||||||
else -> value
|
value is PsiAnnotation -> KtLightAnnotationForSourceEntry(
|
||||||
|
value.qualifiedName!!,
|
||||||
|
ktOriginForElement().let {
|
||||||
|
it.asKtCall() ?: throw UnsupportedOperationException("cant convert $it to KtCallElement")
|
||||||
|
},
|
||||||
|
parent, { value }
|
||||||
|
)
|
||||||
|
else -> LightElementValue(value, parent, ktOriginForElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +208,13 @@ class KtLightAnnotationForSourceEntry(
|
|||||||
|
|
||||||
override fun getName() = null
|
override fun getName() = null
|
||||||
|
|
||||||
override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it, this) }
|
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, {
|
||||||
override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it, this) }
|
getMemberValueAsCallArgument(value, kotlinOrigin)
|
||||||
|
})
|
||||||
|
|
||||||
|
override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||||
|
|
||||||
|
override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||||
|
|
||||||
override fun delete() = kotlinOrigin.delete()
|
override fun delete() = kotlinOrigin.delete()
|
||||||
|
|
||||||
@@ -193,7 +233,7 @@ class KtLightAnnotationForSourceEntry(
|
|||||||
|
|
||||||
class KtLightNonSourceAnnotation(
|
class KtLightNonSourceAnnotation(
|
||||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||||
): KtLightAbstractAnnotation(parent, { clsDelegate }) {
|
) : KtLightAbstractAnnotation(parent, { clsDelegate }) {
|
||||||
override val kotlinOrigin: KtAnnotationEntry? get() = null
|
override val kotlinOrigin: KtAnnotationEntry? get() = null
|
||||||
override fun getQualifiedName() = clsDelegate.qualifiedName
|
override fun getQualifiedName() = clsDelegate.qualifiedName
|
||||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||||
@@ -245,4 +285,11 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne
|
|||||||
|
|
||||||
internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations
|
internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations
|
||||||
|
|
||||||
private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name)
|
private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name)
|
||||||
|
|
||||||
|
private fun KtElement.getResolvedCall(): ResolvedCall<out CallableDescriptor>? {
|
||||||
|
val context = LightClassGenerationSupport.getInstance(this.project).analyze(this)
|
||||||
|
return this.getResolvedCall(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.asJava
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiAnnotation
|
||||||
|
import com.intellij.psi.PsiArrayInitializerMemberValue
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiModifierListOwner
|
||||||
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
import org.jetbrains.uast.java.annotations
|
||||||
|
|
||||||
|
class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
|
|
||||||
|
override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||||
|
|
||||||
|
fun testBooleanAnnotation() {
|
||||||
|
myFixture.addClass("""
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface Autowired {
|
||||||
|
boolean required() default true;
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
class AnnotatedClass{
|
||||||
|
@Autowired
|
||||||
|
lateinit var bean: String
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("Autowired.java", "AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").fields.single()
|
||||||
|
.expectAnnotations(2).single { it.qualifiedName == "Autowired" }
|
||||||
|
val annotationAttributeVal = annotations.findAttributeValue("required") as PsiElement
|
||||||
|
assertTextAndRange("true", annotationAttributeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAnnotationsInAnnotationsDeclarations() {
|
||||||
|
myFixture.addClass("""
|
||||||
|
public @interface OuterAnnotation {
|
||||||
|
InnerAnnotation attribute();
|
||||||
|
@interface InnerAnnotation {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation())
|
||||||
|
open class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
|
||||||
|
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAnnotationsInAnnotationsArrayDeclarations() {
|
||||||
|
myFixture.addClass("""
|
||||||
|
public @interface OuterAnnotation {
|
||||||
|
InnerAnnotation[] attributes();
|
||||||
|
@interface InnerAnnotation {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@OuterAnnotation(attributes = arrayOf(OuterAnnotation.InnerAnnotation()))
|
||||||
|
open class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("attributes") as PsiArrayInitializerMemberValue
|
||||||
|
assertTextAndRange("arrayOf(OuterAnnotation.InnerAnnotation())", annotationAttributeVal)
|
||||||
|
assertTextAndRange("InnerAnnotation()", annotationAttributeVal.initializers[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun testAnnotationsInAnnotationsFinalDeclarations() {
|
||||||
|
myFixture.addClass("""
|
||||||
|
public @interface OuterAnnotation {
|
||||||
|
InnerAnnotation attribute();
|
||||||
|
@interface InnerAnnotation {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation())
|
||||||
|
class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
|
||||||
|
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAnnotationsInAnnotationsInAnnotationsDeclarations() {
|
||||||
|
myFixture.addClass("""
|
||||||
|
public @interface OuterAnnotation {
|
||||||
|
InnerAnnotation attribute();
|
||||||
|
@interface InnerAnnotation {
|
||||||
|
InnerInnerAnnotation attribute();
|
||||||
|
@interface InnerInnerAnnotation {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@OuterAnnotation(attribute = OuterAnnotation.InnerAnnotation(attribute = OuterAnnotation.InnerAnnotation.InnerInnerAnnotation()))
|
||||||
|
open class AnnotatedClass //There is another exception if class is not open
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("OuterAnnotation.java", "AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("attribute") as PsiElement
|
||||||
|
assertTextAndRange("InnerAnnotation(attribute = OuterAnnotation.InnerAnnotation.InnerInnerAnnotation())", annotationAttributeVal)
|
||||||
|
|
||||||
|
annotationAttributeVal as PsiAnnotation
|
||||||
|
val innerAnnotationAttributeVal = annotationAttributeVal.findAttributeValue("attribute") as PsiElement
|
||||||
|
assertTextAndRange("InnerInnerAnnotation()", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinAnnotations() {
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
annotation class Anno1(val anno2: Anno2)
|
||||||
|
annotation class Anno2(val anno3: Anno3)
|
||||||
|
annotation class Anno3
|
||||||
|
|
||||||
|
@Anno1(Anno2(Anno3()))
|
||||||
|
class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("anno2") as PsiElement
|
||||||
|
assertTextAndRange("Anno2(Anno3())", annotationAttributeVal)
|
||||||
|
|
||||||
|
annotationAttributeVal as PsiAnnotation
|
||||||
|
val innerAnnotationAttributeVal = annotationAttributeVal.findAttributeValue("anno3") as PsiElement
|
||||||
|
assertTextAndRange("Anno3()", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinAnnotationWithStringArray() {
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
annotation class Anno(val params: Array<String>)
|
||||||
|
@Anno(arrayOf("abc", "def"))
|
||||||
|
class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("params") as PsiElement
|
||||||
|
assertTextAndRange("arrayOf(\"abc\", \"def\")", annotationAttributeVal)
|
||||||
|
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
assertTextAndRange("\"abc\"", annotationAttributeVal.initializers[0])
|
||||||
|
assertTextAndRange("\"def\"", annotationAttributeVal.initializers[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinAnnotationsArray() {
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
annotation class Anno1(val anno2: Array<Anno2>)
|
||||||
|
annotation class Anno2(val v:Int)
|
||||||
|
|
||||||
|
@Anno1(anno2 = arrayOf(Anno2(1), Anno2(2)))
|
||||||
|
class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||||
|
val annotationAttributeVal = annotations.first().findAttributeValue("anno2") as PsiElement
|
||||||
|
assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
|
||||||
|
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
val innerAnnotationAttributeVal = annotationAttributeVal.initializers[0]
|
||||||
|
assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
|
||||||
|
innerAnnotationAttributeVal as PsiAnnotation
|
||||||
|
val value = innerAnnotationAttributeVal.findAttributeValue("v")!!
|
||||||
|
assertTextAndRange("1", value)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testVarargAnnotation() {
|
||||||
|
|
||||||
|
myFixture.configureByText("Outer.java", """
|
||||||
|
@interface Outer{
|
||||||
|
Inner[] value() default {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface Inner{
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@Outer(Inner())
|
||||||
|
class MyAnnotated {}
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
val annotations = listOf(myFixture.findClass("MyAnnotated")).let {
|
||||||
|
assertEquals(1, it.size)
|
||||||
|
it.first().annotations.apply {
|
||||||
|
assertEquals(1, it.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations[0].let { annotation ->
|
||||||
|
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||||
|
assertTextAndRange("@Outer(Inner())", annotationAttributeVal)
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||||
|
assertTextAndRange("Inner()", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testRepeatableAnnotationsArray() {
|
||||||
|
|
||||||
|
myFixture.configureByText("RAnno.java", """
|
||||||
|
import java.lang.annotation.Repeatable;
|
||||||
|
|
||||||
|
@interface RContainer{
|
||||||
|
RAnno[] value() default {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Repeatable(RContainer.class)
|
||||||
|
public @interface RAnno {
|
||||||
|
String[] value() default {};
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
myFixture.configureByText("AnnotatedClass.kt", """
|
||||||
|
@RAnno()
|
||||||
|
@RAnno("1")
|
||||||
|
@RAnno("1", "2")
|
||||||
|
class AnnotatedClass
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(3)
|
||||||
|
annotations[0].let { annotation ->
|
||||||
|
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||||
|
assertTextAndRange("{}", annotationAttributeVal)
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
TestCase.assertTrue(annotationAttributeVal.initializers.isEmpty())
|
||||||
|
}
|
||||||
|
annotations[1].let { annotation ->
|
||||||
|
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||||
|
assertTextAndRange("@RAnno(\"1\")", annotationAttributeVal)
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||||
|
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
annotations[2].let { annotation ->
|
||||||
|
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||||
|
assertTextAndRange("@RAnno(\"1\", \"2\")", annotationAttributeVal)
|
||||||
|
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||||
|
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||||
|
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
annotationAttributeVal.initializers[1].let { innerAnnotationAttributeVal ->
|
||||||
|
assertTextAndRange("\"2\"", innerAnnotationAttributeVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertTextAndRange(expected: String, psiElement: PsiElement) {
|
||||||
|
TestCase.assertEquals(expected, psiElement.text)
|
||||||
|
TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiModifierListOwner.expectAnnotations(number: Int): Array<PsiAnnotation> =
|
||||||
|
this.annotations.apply {
|
||||||
|
TestCase.assertEquals("expected one annotation, found ${this.joinToString(", ") { it.qualifiedName ?: "unknown" }}",
|
||||||
|
number, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user