lightAnnotations: remove several brittle assertions

Add some test cases with "red" code
This commit is contained in:
Pavel V. Talanov
2017-08-22 15:37:23 +03:00
parent a6b446cc4f
commit 7fecec1141
2 changed files with 87 additions and 32 deletions
@@ -16,7 +16,6 @@
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.*
@@ -53,6 +52,8 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: ()
open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName
} }
private typealias AnnotationValueOrigin = () -> PsiElement?
class KtLightAnnotationForSourceEntry( class KtLightAnnotationForSourceEntry(
private val qualifiedName: String, private val qualifiedName: String,
override val kotlinOrigin: KtCallElement, override val kotlinOrigin: KtCallElement,
@@ -65,9 +66,9 @@ class KtLightAnnotationForSourceEntry(
open inner class LightElementValue<out D : PsiElement>( open inner class LightElementValue<out D : PsiElement>(
val delegate: D, val delegate: D,
private val parent: PsiElement, private val parent: PsiElement,
private val originalExpressionProvider: () -> PsiElement valueOrigin: AnnotationValueOrigin
) : PsiAnnotationMemberValue, PsiElement by delegate { ) : PsiAnnotationMemberValue, PsiElement by delegate {
val originalExpression: PsiElement by lazyPub { originalExpressionProvider() } val originalExpression: PsiElement? by lazyPub(valueOrigin)
fun getConstantValue(): Any? { fun getConstantValue(): Any? {
val expression = originalExpression as? KtExpression ?: return null val expression = originalExpression as? KtExpression ?: return null
@@ -77,12 +78,12 @@ class KtLightAnnotationForSourceEntry(
} }
override fun getReference() = references.singleOrNull() override fun getReference() = references.singleOrNull()
override fun getReferences() = originalExpression.references override fun getReferences() = originalExpression?.references.orEmpty()
override fun getLanguage(): Language = KotlinLanguage.INSTANCE override fun getLanguage() = 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 getText() = originalExpression?.text.orEmpty()
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
@@ -101,24 +102,23 @@ class KtLightAnnotationForSourceEntry(
} }
} }
private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement { private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? {
val resolvedCall = callHolder.getResolvedCall()!! val resolvedCall = callHolder.getResolvedCall() ?: return null
val annotationConstructor = resolvedCall.resultingDescriptor val annotationConstructor = resolvedCall.resultingDescriptor
val parameterName = val parameterName =
memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?: memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?:
memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.name ?: memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.name ?:
"value" "value"
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null
?: error("single parameter '$parameterName' was not found in ${annotationConstructor.valueParameters.map { it.name.asString() }}") val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null
val resolvedArgument = resolvedCall.valueArguments[parameter]!!
return when (resolvedArgument) { return when (resolvedArgument) {
is DefaultValueArgument -> { is DefaultValueArgument -> {
val psi = parameter.source.getPsi() val psi = parameter.source.getPsi()
when (psi) { when (psi) {
is KtParameter -> psi.defaultValue!! is KtParameter -> psi.defaultValue
is PsiAnnotationMethod -> psi.defaultValue!! is PsiAnnotationMethod -> psi.defaultValue
else -> throw UnsupportedOperationException("$psi of type ${psi?.javaClass}") else -> error("$psi of type ${psi?.javaClass}")
} }
} }
@@ -135,7 +135,7 @@ class KtLightAnnotationForSourceEntry(
it.asKtCall() ?: it it.asKtCall() ?: it
} }
else -> throw IllegalArgumentException("resolvedArgument: {$resolvedArgument} cant be processed") else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed")
} }
} }
@@ -149,24 +149,24 @@ class KtLightAnnotationForSourceEntry(
open inner class LightExpressionValue<out D : PsiExpression>( open inner class LightExpressionValue<out D : PsiExpression>(
delegate: D, delegate: D,
parent: PsiElement, parent: PsiElement,
originalExpressionProvider: () -> PsiElement valueOrigin: AnnotationValueOrigin
) : LightElementValue<D>(delegate, parent, originalExpressionProvider), PsiExpression { ) : LightElementValue<D>(delegate, parent, valueOrigin), PsiExpression {
override fun getType(): PsiType? = delegate.type override fun getType(): PsiType? = delegate.type
} }
inner class LightStringLiteral( inner class LightStringLiteral(
delegate: PsiLiteralExpression, delegate: PsiLiteralExpression,
parent: PsiElement, parent: PsiElement,
originalExpressionProvider: () -> PsiElement valueOrigin: AnnotationValueOrigin
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, originalExpressionProvider), PsiLiteralExpression { ) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, valueOrigin), 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,
originalExpressionProvider: () -> PsiElement valueOrigin: AnnotationValueOrigin
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, originalExpressionProvider), PsiClassObjectAccessExpression { ) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, valueOrigin), PsiClassObjectAccessExpression {
override fun getType() = delegate.type override fun getType() = delegate.type
override fun getOperand(): PsiTypeElement = delegate.operand override fun getOperand(): PsiTypeElement = delegate.operand
} }
@@ -174,8 +174,8 @@ class KtLightAnnotationForSourceEntry(
inner class LightArrayInitializerValue( inner class LightArrayInitializerValue(
delegate: PsiArrayInitializerMemberValue, delegate: PsiArrayInitializerMemberValue,
parent: PsiElement, parent: PsiElement,
originalExpressionProvider: () -> PsiElement valueOrigin: AnnotationValueOrigin
) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, originalExpressionProvider), PsiArrayInitializerMemberValue { ) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue {
private val _initializers by lazyPub { private val _initializers by lazyPub {
delegate.initializers.mapIndexed { i, it -> delegate.initializers.mapIndexed { i, it ->
wrapAnnotationValue(it, this, { wrapAnnotationValue(it, this, {
@@ -187,20 +187,20 @@ class KtLightAnnotationForSourceEntry(
override fun getInitializers() = _initializers override fun getInitializers() = _initializers
} }
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOriginForElement: () -> PsiElement): PsiAnnotationMemberValue { private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue {
return when { return when {
value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOriginForElement) value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value, parent, ktOrigin)
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOriginForElement) value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin)
value is PsiExpression -> LightExpressionValue(value, parent, ktOriginForElement) value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin)
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOriginForElement) value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin)
value is PsiAnnotation -> KtLightAnnotationForSourceEntry( value is PsiAnnotation -> KtLightAnnotationForSourceEntry(
value.qualifiedName!!, value.qualifiedName!!,
ktOriginForElement().let { ktOrigin().let {
it.asKtCall() ?: throw UnsupportedOperationException("cant convert $it to KtCallElement") it?.asKtCall() ?: throw UnsupportedOperationException("cant convert $it to KtCallElement")
}, },
parent, { value } parent, { value }
) )
else -> LightElementValue(value, parent, ktOriginForElement) else -> LightElementValue(value, parent, ktOrigin)
} }
} }
@@ -287,6 +287,61 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
} }
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) { private fun assertTextAndRange(expected: String, psiElement: PsiElement) {
TestCase.assertEquals(expected, psiElement.text) TestCase.assertEquals(expected, psiElement.text)
TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text)) TestCase.assertEquals(expected, psiElement.textRange.substring(psiElement.containingFile.text))