mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-04-10 00:21:27 +00:00
Uast: KotlinUNestedAnnotation for processing nested annotations (IDEA-185890)
This commit is contained in:
committed by
xiexed
parent
1f81c0cdfe
commit
48ea52def1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
17
plugins/uast-kotlin/testData/AnnotationComplex.kt
vendored
Normal file
17
plugins/uast-kotlin/testData/AnnotationComplex.kt
vendored
Normal 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
|
||||
30
plugins/uast-kotlin/testData/AnnotationComplex.log.txt
vendored
Normal file
30
plugins/uast-kotlin/testData/AnnotationComplex.log.txt
vendored
Normal 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)
|
||||
26
plugins/uast-kotlin/testData/AnnotationComplex.render.txt
vendored
Normal file
26
plugins/uast-kotlin/testData/AnnotationComplex.render.txt
vendored
Normal 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
|
||||
}
|
||||
@@ -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() }}")
|
||||
|
||||
@@ -72,4 +72,7 @@ class SimpleKotlinRenderLogTest : AbstractKotlinRenderLogTest() {
|
||||
|
||||
@Test
|
||||
fun testAnonymous() = doTest("Anonymous")
|
||||
|
||||
@Test
|
||||
fun testAnnotationComplex() = doTest("AnnotationComplex")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user