Uast: KotlinUNestedAnnotation for processing nested annotations (IDEA-185890)

This commit is contained in:
Nicolay Mitropolsky
2018-02-01 13:13:05 +03:00
committed by xiexed
parent 1f81c0cdfe
commit 48ea52def1
8 changed files with 165 additions and 21 deletions

View File

@@ -27,6 +27,8 @@ import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.elements.*
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
@@ -206,6 +208,17 @@ class KotlinUastLanguagePlugin : UastLanguagePlugin {
is KtFile -> el<UFile> { KotlinUFile(original, this@KotlinUastLanguagePlugin) }
is FakeFileForLightClass -> el<UFile> { KotlinUFile(original.navigationElement, this@KotlinUastLanguagePlugin) }
is KtAnnotationEntry -> el<UAnnotation>(build(::KotlinUAnnotation))
is KtCallExpression ->
if (requiredType != null && UAnnotation::class.java.isAssignableFrom(requiredType)) {
el<UAnnotation> {
val classDescriptor =
(original.getResolvedCall(original.analyze())?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass
if (classDescriptor?.kind == ClassKind.ANNOTATION_CLASS)
KotlinUNestedAnnotation(original, givenParent, classDescriptor)
else
null
}
} else null
is KtLightAnnotationForSourceEntry -> convertElement(original.kotlinOrigin, givenParent, requiredType)
else -> null
}

View File

@@ -1,37 +1,44 @@
package org.jetbrains.uast.kotlin
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import org.jetbrains.kotlin.asJava.toLightAnnotation
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.uast.*
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
class KotlinUAnnotation(
override val psi: KtAnnotationEntry,
givenParent: UElement?
abstract class KotlinUAnnotationBase(
final override val psi: KtElement,
givenParent: UElement?
) : KotlinAbstractUElement(givenParent), UAnnotation {
override val javaPsi = psi.toLightAnnotation()
abstract override val javaPsi: PsiAnnotation?
override val sourcePsi = psi
final override val sourcePsi = psi
private val resolvedAnnotation: AnnotationDescriptor? by lz { psi.analyze()[BindingContext.ANNOTATION, psi] }
protected abstract fun annotationUseSiteTarget(): AnnotationUseSiteTarget?
private val resolvedCall: ResolvedCall<*>? by lz { psi.getResolvedCall(psi.analyze()) }
override val qualifiedName: String?
get() = resolvedAnnotation?.fqName?.asString()
get() = annotationClassDescriptor.takeUnless(ErrorUtils::isError)
?.fqNameUnsafe
?.takeIf(FqNameUnsafe::isSafe)
?.toSafe()
?.toString()
override val attributeValues: List<UNamedExpression> by lz {
resolvedCall?.valueArguments?.entries?.mapNotNull {
@@ -47,13 +54,15 @@ class KotlinUAnnotation(
} ?: emptyList()
}
protected abstract val annotationClassDescriptor: ClassDescriptor?
override fun resolve(): PsiClass? {
val descriptor = resolvedAnnotation?.annotationClass ?: return null
val descriptor = annotationClassDescriptor ?: return null
return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass
}
override fun findAttributeValue(name: String?): UExpression? =
findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value")
findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value")
fun findAttributeValueExpression(arg: ValueArgument): UExpression? {
val mapping = resolvedCall?.getArgumentMapping(arg)
@@ -66,17 +75,16 @@ class KotlinUAnnotation(
override fun findDeclaredAttributeValue(name: String?): UExpression? {
return attributeValues.find {
it.name == name ||
(name == null && it.name == "value") ||
(name == "value" && it.name == null)
(name == null && it.name == "value") ||
(name == "value" && it.name == null)
}?.expression
}
private fun findAttributeDefaultValue(name: String): UExpression? {
val parameter = resolvedAnnotation
?.annotationClass
?.unsubstitutedPrimaryConstructor
?.valueParameters
?.find { it.name.asString() == name } ?: return null
val parameter = annotationClassDescriptor
?.unsubstitutedPrimaryConstructor
?.valueParameters
?.find { it.name.asString() == name } ?: return null
val defaultValue = (parameter.source.getPsi() as? KtParameter)?.defaultValue ?: return null
return getLanguagePlugin().convertWithParent(defaultValue)
@@ -84,7 +92,7 @@ class KotlinUAnnotation(
override fun convertParent(): UElement? {
val superParent = super.convertParent() ?: return null
if (psi.useSiteTarget?.getAnnotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER) {
if (annotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER) {
(superParent.uastParent as? KotlinUMethod)?.uastParameters?.firstIsInstance<KotlinReceiverUParameter>()?.let {
return it
}
@@ -93,3 +101,32 @@ class KotlinUAnnotation(
}
}
class KotlinUAnnotation(
val annotationEntry: KtAnnotationEntry,
givenParent: UElement?
) : KotlinUAnnotationBase(annotationEntry, givenParent), UAnnotation {
override val javaPsi = annotationEntry.toLightAnnotation()
private val resolvedAnnotation: AnnotationDescriptor? by lz { annotationEntry.analyze()[BindingContext.ANNOTATION, annotationEntry] }
override val annotationClassDescriptor: ClassDescriptor?
get() = resolvedAnnotation?.annotationClass
override fun annotationUseSiteTarget() = annotationEntry.useSiteTarget?.getAnnotationUseSiteTarget()
}
class KotlinUNestedAnnotation(
original: KtCallExpression,
givenParent: UElement?,
private val classDescriptor: ClassDescriptor?
) : KotlinUAnnotationBase(original, givenParent) {
override val javaPsi: PsiAnnotation? = null
override val annotationClassDescriptor: ClassDescriptor?
get() = classDescriptor
override fun annotationUseSiteTarget(): AnnotationUseSiteTarget? = null
}

View File

@@ -0,0 +1,17 @@
annotation class Annotation
@Annotation
class A
annotation class AnnotationInner(val value: Annotation)
@AnnotationArray(Annotation())
class B1
@AnnotationArray(value = Annotation())
class B2
annotation class AnnotationArray(vararg val value: Annotation)
@AnnotationArray(Annotation())
class C

View File

@@ -0,0 +1,30 @@
UFile (package = )
UClass (name = Annotation)
UClass (name = A)
UAnnotation (fqName = Annotation)
UAnnotationMethod (name = A)
UClass (name = AnnotationInner)
UAnnotationMethod (name = value)
UClass (name = B1)
UAnnotation (fqName = AnnotationArray)
UNamedExpression (name = value)
UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))
UIdentifier (Identifier (Annotation))
USimpleNameReferenceExpression (identifier = <init>)
UAnnotationMethod (name = B1)
UClass (name = B2)
UAnnotation (fqName = AnnotationArray)
UNamedExpression (name = value)
UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))
UIdentifier (Identifier (Annotation))
USimpleNameReferenceExpression (identifier = <init>)
UAnnotationMethod (name = B2)
UClass (name = AnnotationArray)
UAnnotationMethod (name = value)
UClass (name = C)
UAnnotation (fqName = AnnotationArray)
UNamedExpression (name = value)
UCallExpression (kind = UastCallKind(name='constructor_call'), argCount = 0))
UIdentifier (Identifier (Annotation))
USimpleNameReferenceExpression (identifier = <init>)
UAnnotationMethod (name = C)

View File

@@ -0,0 +1,26 @@
public abstract annotation Annotation {
}
public final class A {
public fun A() = UastEmptyExpression
}
public abstract annotation AnnotationInner {
public abstract fun value() : Annotation = UastEmptyExpression
}
public final class B1 {
public fun B1() = UastEmptyExpression
}
public final class B2 {
public fun B2() = UastEmptyExpression
}
public abstract annotation AnnotationArray {
public abstract fun value() : Annotation[] = UastEmptyExpression
}
public final class C {
public fun C() = UastEmptyExpression
}

View File

@@ -275,6 +275,23 @@ class KotlinUastApiTest : AbstractKotlinUastTest() {
}
}
@Test
fun testNestedAnnotation() = doTest("AnnotationComplex") { _, file ->
file.findElementByTextFromPsi<UElement>("@AnnotationArray(value = Annotation())")
.findElementByTextFromPsi<UElement>("Annotation()")
.sourcePsiElement
.let { referenceExpression ->
val convertedUAnnotation = referenceExpression
.cast<KtReferenceExpression>()
.toUElementOfType<UAnnotation>()
?: throw AssertionError("haven't got annotation from $referenceExpression(${referenceExpression?.javaClass})")
assertEquals("Annotation", convertedUAnnotation.qualifiedName)
}
}
}
fun <T, R> Iterable<T>.assertedFind(value: R, transform: (T) -> R): T = find { transform(it) == value } ?: throw AssertionError("'$value' not found, only ${this.joinToString { transform(it).toString() }}")

View File

@@ -72,4 +72,7 @@ class SimpleKotlinRenderLogTest : AbstractKotlinRenderLogTest() {
@Test
fun testAnonymous() = doTest("Anonymous")
@Test
fun testAnnotationComplex() = doTest("AnnotationComplex")
}

View File

@@ -73,7 +73,8 @@ inline fun <reified T : UElement> PsiElement.findUElementByTextFromPsi(refText:
?: throw AssertionError("requested text '$refText' was not found in $this")
val uElementContainingText = elementAtStart.parentsWithSelf.let {
if (strict) it.dropWhile { !it.text.contains(refText) } else it
}.mapNotNull { it.toUElementOfType<T>() }.first()
}.mapNotNull { it.toUElementOfType<T>() }.firstOrNull()
?: throw AssertionError("requested text '$refText' not found as '${T::class.java.canonicalName}' in $this")
if (strict && uElementContainingText.psi != null && uElementContainingText.psi?.text != refText) {
throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText")
}