diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt index 146e6314d8d..fb0443ab2da 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.asJava.elements +import com.intellij.lang.Language import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.* @@ -24,19 +25,21 @@ import org.jetbrains.annotations.Nullable import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.asJava.classes.cannotModify import org.jetbrains.kotlin.asJava.classes.lazyPub +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.idea.KotlinLanguage 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.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument 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.source.getPsi import org.jetbrains.kotlin.types.TypeUtils abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) : - KtLightElementBase(parent), PsiAnnotation, KtLightElement { + KtLightElementBase(parent), PsiAnnotation, KtLightElement { override val clsDelegate by lazyPub(computeDelegate) override fun getNameReferenceElement() = clsDelegate.nameReferenceElement @@ -52,48 +55,19 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () class KtLightAnnotationForSourceEntry( private val qualifiedName: String, - override val kotlinOrigin: KtAnnotationEntry, - parent: KtLightElement<*, *>, + override val kotlinOrigin: KtCallElement, + parent: PsiElement, computeDelegate: () -> PsiAnnotation ) : KtLightAbstractAnnotation(parent, computeDelegate) { override fun getQualifiedName() = qualifiedName - open inner class LightExpressionValue( + open inner class LightElementValue( val delegate: D, - private val parent: PsiElement - ) : PsiAnnotationMemberValue, PsiExpression by delegate { - val originalExpression: PsiElement? by lazyPub { - val nameAndValue = delegate.getStrictParentOfType() ?: return@lazyPub null - 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 - } - } + private val parent: PsiElement, + private val originalExpressionProvider: () -> PsiElement + ) : PsiAnnotationMemberValue, PsiElement by delegate { + val originalExpression: PsiElement by lazyPub { originalExpressionProvider() } fun getConstantValue(): Any? { 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) } - private fun unwrapArray(arguments: List): 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 getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY - override fun getLanguage() = KotlinLanguage.INSTANCE + override fun getReferences() = originalExpression.references + override fun getLanguage(): Language = KotlinLanguage.INSTANCE 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 getText(): String = originalExpression.text override fun replace(newElement: PsiElement): PsiElement { val value = (newElement as? PsiLiteral)?.value as? String ?: return this - val origin = originalExpression ?: return this + val origin = originalExpression val exprToReplace = 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()?.name ?: + memberValue.getNonStrictParentOfType()?.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): 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( + delegate: D, + parent: PsiElement, + originalExpressionProvider: () -> PsiElement + ) : LightElementValue(delegate, parent, originalExpressionProvider), PsiExpression { + override fun getType(): PsiType? = delegate.type + } + inner class LightStringLiteral( delegate: PsiLiteralExpression, - parent: PsiElement - ) : LightExpressionValue(delegate, parent), PsiLiteralExpression { + parent: PsiElement, + originalExpressionProvider: () -> PsiElement + ) : LightExpressionValue(delegate, parent, originalExpressionProvider), PsiLiteralExpression { override fun getValue() = delegate.value } inner class LightClassLiteral( delegate: PsiClassObjectAccessExpression, - parent: PsiElement - ) : LightExpressionValue(delegate, parent), PsiClassObjectAccessExpression { + parent: PsiElement, + originalExpressionProvider: () -> PsiElement + ) : LightExpressionValue(delegate, parent, originalExpressionProvider), PsiClassObjectAccessExpression { override fun getType() = delegate.type override fun getOperand(): PsiTypeElement = delegate.operand } inner class LightArrayInitializerValue( - private val delegate: PsiArrayInitializerMemberValue, - private val parent: PsiElement - ) : PsiArrayInitializerMemberValue by delegate { - private val _initializers by lazyPub { delegate.initializers.map { wrapAnnotationValue(it, this) }.toTypedArray() } + delegate: PsiArrayInitializerMemberValue, + parent: PsiElement, + originalExpressionProvider: () -> PsiElement + ) : LightElementValue(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 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 { - value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent) - value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent) - value is PsiExpression -> LightExpressionValue(value, parent) - value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent) - else -> value + value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOriginForElement) + value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOriginForElement) + value is PsiExpression -> LightExpressionValue(value, parent, ktOriginForElement) + value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOriginForElement) + 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 findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it, this) } - override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it, this) } + private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, 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() @@ -193,7 +233,7 @@ class KtLightAnnotationForSourceEntry( class KtLightNonSourceAnnotation( parent: PsiElement, clsDelegate: PsiAnnotation -): KtLightAbstractAnnotation(parent, { clsDelegate }) { +) : KtLightAbstractAnnotation(parent, { clsDelegate }) { override val kotlinOrigin: KtAnnotationEntry? get() = null override fun getQualifiedName() = clsDelegate.qualifiedName override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() @@ -245,4 +285,11 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations -private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name) \ No newline at end of file +private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name) + +private fun KtElement.getResolvedCall(): ResolvedCall? { + 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 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt new file mode 100644 index 00000000000..d749074e522 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt @@ -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) + @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) + 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 = + this.annotations.apply { + TestCase.assertEquals("expected one annotation, found ${this.joinToString(", ") { it.qualifiedName ?: "unknown" }}", + number, size) + } + +} \ No newline at end of file