diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.181 b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.181 new file mode 100644 index 00000000000..16b490a185c --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/elements/lightAnnotations.kt.181 @@ -0,0 +1,379 @@ +/* + * Copyright 2010-2016 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.elements + +import com.intellij.openapi.diagnostic.Attachment +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.TextRange +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.* +import org.jetbrains.annotations.NotNull +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.descriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.psi.* +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.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.types.TypeUtils + +private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations") + +abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) : + KtLightElementBase(parent), PsiAnnotation, KtLightElement { + override val clsDelegate by lazyPub(computeDelegate) + + override fun getNameReferenceElement() = clsDelegate.nameReferenceElement + + override fun getOwner() = parent as? PsiAnnotationOwner + + override fun getMetaData() = clsDelegate.metaData + + override fun getParameterList() = clsDelegate.parameterList + + override fun canNavigate(): Boolean = super.canNavigate() + + override fun canNavigateToSource(): Boolean = super.canNavigateToSource() + + override fun navigate(requestFocus: Boolean) = super.navigate(requestFocus) + + open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName +} + +private typealias AnnotationValueOrigin = () -> PsiElement? + +class KtLightAnnotationForSourceEntry( + private val qualifiedName: String, + override val kotlinOrigin: KtCallElement, + parent: PsiElement, + computeDelegate: () -> PsiAnnotation +) : KtLightAbstractAnnotation(parent, computeDelegate) { + + override fun getQualifiedName() = qualifiedName + + open inner class LightElementValue( + val delegate: D, + private val parent: PsiElement, + valueOrigin: AnnotationValueOrigin + ) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate { + override fun getMirror(): PsiElement = delegate + + val originalExpression: PsiElement? by lazyPub(valueOrigin) + + fun getConstantValue(): Any? { + val expression = originalExpression as? KtExpression ?: return null + val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin + val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry) + return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE) + } + + override fun getReference() = references.singleOrNull() + override fun getReferences() = originalExpression?.references.orEmpty() + override fun getLanguage() = KotlinLanguage.INSTANCE + override fun getNavigationElement() = originalExpression + override fun isPhysical(): Boolean = false + override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE + override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0 + override fun getParent() = parent + override fun getText() = originalExpression?.text.orEmpty() + override fun getContainingFile(): PsiFile? = if (originalExpression?.containingFile == kotlinOrigin.containingFile) + kotlinOrigin.containingFile else delegate.containingFile + + override fun replace(newElement: PsiElement): PsiElement { + val value = (newElement as? PsiLiteral)?.value as? String ?: return this + val origin = originalExpression + + val exprToReplace = + if (origin is KtCallExpression /*arrayOf*/) { + unwrapArray(origin.valueArguments) + } + else { + origin as? KtExpression + } ?: return this + exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\"")) + + return this + } + } + + private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? { + val resolvedCall = callHolder.getResolvedCall() ?: return null + val annotationConstructor = resolvedCall.resultingDescriptor + val parameterName = + memberValue.getNonStrictParentOfType()?.name ?: + memberValue.getNonStrictParentOfType()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?: + "value" + + val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null + val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null + return when (resolvedArgument) { + is DefaultValueArgument -> { + val psi = parameter.source.getPsi() + when (psi) { + is KtParameter -> psi.defaultValue + is PsiAnnotationMethod -> psi.defaultValue + else -> error("$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().let { + (it as? KtValueArgument) + ?.takeIf { + it.getSpreadElement() != null || + it.getArgumentName() != null || + it.getArgumentExpression() is KtCollectionLiteralExpression + } + ?.getArgumentExpression() ?: it.parent + } + + else -> error("resolvedArgument: ${resolvedArgument.javaClass} 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, + valueOrigin: AnnotationValueOrigin + ) : LightElementValue(delegate, parent, valueOrigin), PsiExpression { + override fun getType(): PsiType? = delegate.type + } + + inner class LightPsiLiteral( + delegate: PsiLiteralExpression, + parent: PsiElement, + valueOrigin: AnnotationValueOrigin + ) : LightExpressionValue(delegate, parent, valueOrigin), PsiLiteralExpression { + override fun getValue() = delegate.value + } + + inner class LightClassLiteral( + delegate: PsiClassObjectAccessExpression, + parent: PsiElement, + valueOrigin: AnnotationValueOrigin + ) : LightExpressionValue(delegate, parent, valueOrigin), PsiClassObjectAccessExpression { + override fun getType() = delegate.type + override fun getOperand(): PsiTypeElement = delegate.operand + } + + inner class LightArrayInitializerValue( + delegate: PsiArrayInitializerMemberValue, + parent: PsiElement, + valueOrigin: AnnotationValueOrigin + ) : LightElementValue(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue { + private val _initializers by lazyPub { + delegate.initializers.mapIndexed { i, memberValue -> + wrapAnnotationValue(memberValue, this, { + originalExpression.let { ktOrigin -> + when { + ktOrigin is KtCallExpression + && memberValue is PsiAnnotation + && isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin + ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression() + ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression() + ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i) + delegate.initializers.size == 1 -> ktOrigin + else -> null + }.also { + if (it == null) + LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}", + Attachment( + "source_fragments.txt", + "origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}" + ) + ) + } + } + }) + }.toTypedArray() + } + + override fun getInitializers() = _initializers + } + + private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue = + when { + value is PsiLiteralExpression -> LightPsiLiteral(value, parent, ktOrigin) + value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin) + value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin) + value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin) + value is PsiAnnotation -> { + val origin = ktOrigin() + val ktCallElement = origin?.asKtCall() + val qualifiedName = value.qualifiedName + if (qualifiedName != null && ktCallElement != null) + KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value }) + else { + LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})", + Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'")) + LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead? + } + } + else -> LightElementValue(value, parent, ktOrigin) + } + + override fun getName() = null + + 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 getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList()) + + inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this), + PsiAnnotationParameterList { + override val kotlinOrigin get() = null + override fun getAttributes(): Array = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray() + + inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this), + PsiNameValuePair { + override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue) + override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier + override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString() + override val kotlinOrigin: KtElement? = null + + override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) } + } + } + + + override fun delete() = kotlinOrigin.delete() + + override fun toString() = "@$qualifiedName" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class.java != this::class.java) return false + return kotlinOrigin == (other as KtLightAnnotationForSourceEntry).kotlinOrigin + } + + override fun hashCode() = kotlinOrigin.hashCode() + + override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() +} + +class KtLightNonSourceAnnotation( + parent: PsiElement, clsDelegate: PsiAnnotation +) : KtLightAbstractAnnotation(parent, { clsDelegate }) { + override val kotlinOrigin: KtAnnotationEntry? get() = null + override fun getQualifiedName() = clsDelegate.qualifiedName + override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() + override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName) + override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName) +} + +class KtLightNonExistentAnnotation(parent: KtLightElement<*, *>) : KtLightElementBase(parent), PsiAnnotation { + override val kotlinOrigin get() = null + override fun toString() = this.javaClass.name + + override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() + + override fun getNameReferenceElement() = null + override fun findAttributeValue(attributeName: String?) = null + override fun getQualifiedName() = null + override fun getOwner() = parent as? PsiAnnotationOwner + override fun findDeclaredAttributeValue(attributeName: String?) = null + override fun getMetaData() = null + override fun getParameterList() = KtLightEmptyAnnotationParameterList(this) + + override fun canNavigate(): Boolean = super.canNavigate() + + override fun canNavigateToSource(): Boolean = super.canNavigateToSource() + + override fun navigate(requestFocus: Boolean) = super.navigate(requestFocus) +} + +class KtLightEmptyAnnotationParameterList(parent: PsiElement) : KtLightElementBase(parent), PsiAnnotationParameterList { + override val kotlinOrigin get() = null + override fun getAttributes(): Array = emptyArray() +} + +class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwner>, parent: PsiElement) : KtLightAbstractAnnotation(parent, { + // searching for last because nullability annotations are generated after backend generates source annotations + member.clsDelegate.modifierList?.annotations?.findLast { + isNullabilityAnnotation(it.qualifiedName) + } ?: KtLightNonExistentAnnotation(member) +}) { + override fun fqNameMatches(fqName: String): Boolean { + if (!isNullabilityAnnotation(fqName)) return false + + return super.fqNameMatches(fqName) + } + + override val kotlinOrigin get() = null + override fun setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify() + + override fun findAttributeValue(attributeName: String?) = null + + override fun getQualifiedName(): String? = clsDelegate.qualifiedName + + override fun findDeclaredAttributeValue(attributeName: String?) = null +} + +internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations + +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 isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) = + (callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName + +private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement + +private fun psiReport(psiElement: PsiElement?): String { + if (psiElement == null) return "null" + val text = try { + psiElement.text + } + catch (e: Exception) { + "${e.javaClass.simpleName}:${e.message}" + } + return "${psiElement.javaClass.canonicalName}[$text]" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.181 b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.181 new file mode 100644 index 00000000000..fac4a44d2f4 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/asJava/KtLightAnnotationTest.kt.181 @@ -0,0 +1,534 @@ +/* + * 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.openapi.application.WriteAction +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl +import com.intellij.psi.* +import com.intellij.testFramework.LightProjectDescriptor +import junit.framework.TestCase +import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.idea.completion.test.assertInstanceOf +import org.jetbrains.kotlin.idea.facet.configureFacet +import org.jetbrains.kotlin.idea.facet.getOrCreateFacet +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor + +class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() { + + override fun getProjectDescriptor(): LightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + + fun testBooleanAnnotationDefaultValue() { + 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 + assertTextRangeAndValue("true", true, annotationAttributeVal) + } + + fun testStringAnnotationWithUnnamedParameter() { + myFixture.addClass(""" + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + + @Target(ElementType.PARAMETER) + public @interface Qualifier { + String value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + class AnnotatedClass { + fun bar(@Qualifier("foo") param: String){} + } + """.trimIndent()) + myFixture.testHighlighting("Qualifier.java", "AnnotatedClass.kt") + + val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single() + .expectAnnotations(2).single { it.qualifiedName == "Qualifier" } + val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement + assertTextRangeAndValue("\"foo\"", "foo", 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) + assertIsKtLightAnnotation("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 testKotlinAnnotationWithStringArrayLiteral() { + configureKotlinVersion("1.2") + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno(val params: Array) + @Anno(params = ["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("[\"abc\", \"def\"]", annotationAttributeVal) + + annotationAttributeVal as PsiArrayInitializerMemberValue + assertTextAndRange("\"abc\"", annotationAttributeVal.initializers[0].assertInstanceOf()) + 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 annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single() + + val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue + assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal) + annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal -> + assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal) + assertIsKtLightAnnotation("Anno2(1)", innerAnnotationAttributeVal) + innerAnnotationAttributeVal as PsiAnnotation + val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf() + assertTextAndRange("1", value) + } + + + val attributeValueFromParameterList = annotation.parameterList.attributes.single().value as PsiArrayInitializerMemberValue + assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", attributeValueFromParameterList) + attributeValueFromParameterList.initializers[0].let { innerAnnotationAttributeVal -> + assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal) + assertIsKtLightAnnotation("Anno2(1)", innerAnnotationAttributeVal) + } + + } + + fun testVarargAnnotation() { + + myFixture.configureByText("Outer.java", """ + @interface Outer{ + Inner[] value() default {}; + } + + @interface Inner{ + } + """) + myFixture.configureByText("AnnotatedClass.kt", """ + @Outer(Inner()) + class MyAnnotated {} + """.trimIndent()) + + val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1) + annotations[0].let { annotation -> + val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement + assertTextAndRange("(Inner())", annotationAttributeVal) + annotationAttributeVal as PsiArrayInitializerMemberValue + annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal -> + assertTextAndRange("Inner()", innerAnnotationAttributeVal) + assertIsKtLightAnnotation("Inner()", innerAnnotationAttributeVal) + } + } + + } + + fun testVarargWithSpread() { + myFixture.addClass(""" + public @interface Annotation { + String[] value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + @Annotation(value = *arrayOf("a", "b", "c")) + open class AnnotatedClass + """.trimIndent()) + myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt") + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue + assertTextAndRange("arrayOf(\"a\", \"b\", \"c\")", annotationAttributeVal) + for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) { + assertTextAndRange(arg, annotationAttributeVal.initializers[i]) + } + } + + fun testVarargWithSpreadComplex() { + myFixture.addClass(""" + public @interface Annotation { + String[] value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + @Annotation(value = arrayOf(*arrayOf("a", "b"), "c", *arrayOf("d", "e"))) + open class AnnotatedClass + """.trimIndent()) + myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt") + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue + assertTextAndRange("arrayOf(*arrayOf(\"a\", \"b\"), \"c\", *arrayOf(\"d\", \"e\"))", annotationAttributeVal) + for ((i, arg) in listOf("arrayOf(\"a\", \"b\")", "\"c\"", "arrayOf(\"d\", \"e\")").withIndex()) { + assertTextAndRange(arg, annotationAttributeVal.initializers[i]) + } + } + + fun testVarargWithArrayLiteral() { + myFixture.addClass(""" + public @interface Annotation { + String[] value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + @Annotation(value = ["a", "b", "c"]) + open class AnnotatedClass + """.trimIndent()) + myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt") + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue + assertTextAndRange("[\"a\", \"b\", \"c\"]", annotationAttributeVal) + for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) { + assertTextAndRange(arg, annotationAttributeVal.initializers[i]) + } + } + + fun testVarargWithSingleArg() { + myFixture.addClass(""" + public @interface Annotation { + String[] value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + @Annotation(value = "a") + open class AnnotatedClass + """.trimIndent()) + myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt") + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue + assertTextAndRange("\"a\"", annotationAttributeVal) + for ((i, arg) in listOf("\"a\"").withIndex()) { + assertTextAndRange(arg, annotationAttributeVal.initializers[i]) + } + } + + fun testVarargWithArrayLiteralAndSpread() { + myFixture.addClass(""" + public @interface Annotation { + String[] value(); + } + """.trimIndent()) + + myFixture.configureByText("AnnotatedClass.kt", """ + @Annotation(*["a", "b", "c"]) + open class AnnotatedClass + """.trimIndent()) + myFixture.testHighlighting("Annotation.java", "AnnotatedClass.kt") + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiArrayInitializerMemberValue + assertTextAndRange("[\"a\", \"b\", \"c\"]", annotationAttributeVal) + for ((i, arg) in listOf("\"a\"", "\"b\"", "\"c\"").withIndex()) { + assertTextAndRange(arg, annotationAttributeVal.initializers[i]) + } + } + + 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("(\"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("(\"1\", \"2\")", annotationAttributeVal) + annotationAttributeVal as PsiArrayInitializerMemberValue + annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal -> + assertTextAndRange("\"1\"", innerAnnotationAttributeVal) + } + annotationAttributeVal.initializers[1].let { innerAnnotationAttributeVal -> + assertTextAndRange("\"2\"", innerAnnotationAttributeVal) + } + } + + } + + fun testWrongNamesPassed() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i:Int , val j: Int) + + @Anno1(k = 3, l = 5) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + TestCase.assertNull(annotation.findAttributeValue("k")) + TestCase.assertNull(annotation.findAttributeValue("l")) + TestCase.assertNull(annotation.findAttributeValue("i")) + TestCase.assertNull(annotation.findAttributeValue("j")) + } + + fun testWrongValuesPassed() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i: Int , val j: Int) + + @Anno1(i = true, j = false) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + assertTextAndRange("true", annotation.findAttributeValue("i")!!) + assertTextAndRange("false", annotation.findAttributeValue("j")!!) + } + + fun testDuplicateParameters() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i:Int , val i: Boolean) + + @Anno1(i = true, i = 3) + class AnnotatedClass + """.trimIndent()) + + val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + val annotation = annotations.first() + assertTextAndRange("", annotation.findAttributeValue("i")!!) + } + + fun testMissingDefault() { + myFixture.configureByText("AnnotatedClass.kt", """ + annotation class Anno1(val i: Int = 0) + + @Anno1() + class AnnotatedClass + """.trimIndent()) + + val (annotation) = myFixture.findClass("AnnotatedClass").expectAnnotations(1) + assertTextAndRange("0", annotation.findAttributeValue("i")!!) + } + + private fun assertTextAndRange(expected: String, psiElement: PsiElement) { + TestCase.assertEquals("mismatch for $psiElement of ${psiElement.javaClass}", expected, psiElement.text) + TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text)) + TestCase.assertEquals(psiElement, PsiAnchor.create(psiElement).retrieve()) + } + + private fun assertIsKtLightAnnotation(expected: String, psiElement: PsiElement) { + TestCase.assertEquals(expected, (psiElement as KtLightAnnotationForSourceEntry).kotlinOrigin.text) + } + + private fun assertTextRangeAndValue(expected: String, value: Any?, psiElement: PsiElement) { + assertTextAndRange(expected, psiElement) + val result = JavaPsiFacade.getInstance(project).constantEvaluationHelper.computeConstantExpression(psiElement) + TestCase.assertEquals(value, result) + val smartPointer = SmartPointerManager.getInstance(psiElement.project).createSmartPsiElementPointer(psiElement) + assertTextAndRange(expected, smartPointer.element!!) + } + + private fun PsiModifierListOwner.expectAnnotations(number: Int): Array = + this.modifierList!!.annotations.apply { + TestCase.assertEquals("expected one annotation, found ${this.joinToString(", ") { it.qualifiedName ?: "unknown" }}", + number, size) + } + + private fun configureKotlinVersion(version: String) { + WriteAction.run { + val modelsProvider = IdeModifiableModelsProviderImpl(project) + val facet = module.getOrCreateFacet(modelsProvider, useProjectSettings = false) + facet.configureFacet(version, LanguageFeature.State.DISABLED, null, modelsProvider) + modelsProvider.commit() + } + } + +} \ No newline at end of file