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.elements.*
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
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.ConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
@@ -206,6 +208,17 @@ class KotlinUastLanguagePlugin : UastLanguagePlugin {
|
|||||||
is KtFile -> el<UFile> { KotlinUFile(original, this@KotlinUastLanguagePlugin) }
|
is KtFile -> el<UFile> { KotlinUFile(original, this@KotlinUastLanguagePlugin) }
|
||||||
is FakeFileForLightClass -> el<UFile> { KotlinUFile(original.navigationElement, this@KotlinUastLanguagePlugin) }
|
is FakeFileForLightClass -> el<UFile> { KotlinUFile(original.navigationElement, this@KotlinUastLanguagePlugin) }
|
||||||
is KtAnnotationEntry -> el<UAnnotation>(build(::KotlinUAnnotation))
|
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)
|
is KtLightAnnotationForSourceEntry -> convertElement(original.kotlinOrigin, givenParent, requiredType)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-20
@@ -1,37 +1,44 @@
|
|||||||
package org.jetbrains.uast.kotlin
|
package org.jetbrains.uast.kotlin
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiAnnotation
|
||||||
import com.intellij.psi.PsiClass
|
import com.intellij.psi.PsiClass
|
||||||
import org.jetbrains.kotlin.asJava.toLightAnnotation
|
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.AnnotationDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
|
||||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||||
import org.jetbrains.kotlin.psi.KtParameter
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.ValueArgument
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
|
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.resolve.source.getPsi
|
||||||
|
import org.jetbrains.kotlin.types.ErrorUtils
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
import org.jetbrains.uast.*
|
import org.jetbrains.uast.*
|
||||||
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
|
import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
|
||||||
|
|
||||||
class KotlinUAnnotation(
|
abstract class KotlinUAnnotationBase(
|
||||||
override val psi: KtAnnotationEntry,
|
final override val psi: KtElement,
|
||||||
givenParent: UElement?
|
givenParent: UElement?
|
||||||
) : KotlinAbstractUElement(givenParent), UAnnotation {
|
) : 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()) }
|
private val resolvedCall: ResolvedCall<*>? by lz { psi.getResolvedCall(psi.analyze()) }
|
||||||
|
|
||||||
override val qualifiedName: String?
|
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 {
|
override val attributeValues: List<UNamedExpression> by lz {
|
||||||
resolvedCall?.valueArguments?.entries?.mapNotNull {
|
resolvedCall?.valueArguments?.entries?.mapNotNull {
|
||||||
@@ -47,13 +54,15 @@ class KotlinUAnnotation(
|
|||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract val annotationClassDescriptor: ClassDescriptor?
|
||||||
|
|
||||||
override fun resolve(): PsiClass? {
|
override fun resolve(): PsiClass? {
|
||||||
val descriptor = resolvedAnnotation?.annotationClass ?: return null
|
val descriptor = annotationClassDescriptor ?: return null
|
||||||
return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass
|
return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findAttributeValue(name: String?): UExpression? =
|
override fun findAttributeValue(name: String?): UExpression? =
|
||||||
findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value")
|
findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value")
|
||||||
|
|
||||||
fun findAttributeValueExpression(arg: ValueArgument): UExpression? {
|
fun findAttributeValueExpression(arg: ValueArgument): UExpression? {
|
||||||
val mapping = resolvedCall?.getArgumentMapping(arg)
|
val mapping = resolvedCall?.getArgumentMapping(arg)
|
||||||
@@ -66,17 +75,16 @@ class KotlinUAnnotation(
|
|||||||
override fun findDeclaredAttributeValue(name: String?): UExpression? {
|
override fun findDeclaredAttributeValue(name: String?): UExpression? {
|
||||||
return attributeValues.find {
|
return attributeValues.find {
|
||||||
it.name == name ||
|
it.name == name ||
|
||||||
(name == null && it.name == "value") ||
|
(name == null && it.name == "value") ||
|
||||||
(name == "value" && it.name == null)
|
(name == "value" && it.name == null)
|
||||||
}?.expression
|
}?.expression
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findAttributeDefaultValue(name: String): UExpression? {
|
private fun findAttributeDefaultValue(name: String): UExpression? {
|
||||||
val parameter = resolvedAnnotation
|
val parameter = annotationClassDescriptor
|
||||||
?.annotationClass
|
?.unsubstitutedPrimaryConstructor
|
||||||
?.unsubstitutedPrimaryConstructor
|
?.valueParameters
|
||||||
?.valueParameters
|
?.find { it.name.asString() == name } ?: return null
|
||||||
?.find { it.name.asString() == name } ?: return null
|
|
||||||
|
|
||||||
val defaultValue = (parameter.source.getPsi() as? KtParameter)?.defaultValue ?: return null
|
val defaultValue = (parameter.source.getPsi() as? KtParameter)?.defaultValue ?: return null
|
||||||
return getLanguagePlugin().convertWithParent(defaultValue)
|
return getLanguagePlugin().convertWithParent(defaultValue)
|
||||||
@@ -84,7 +92,7 @@ class KotlinUAnnotation(
|
|||||||
|
|
||||||
override fun convertParent(): UElement? {
|
override fun convertParent(): UElement? {
|
||||||
val superParent = super.convertParent() ?: return null
|
val superParent = super.convertParent() ?: return null
|
||||||
if (psi.useSiteTarget?.getAnnotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER) {
|
if (annotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER) {
|
||||||
(superParent.uastParent as? KotlinUMethod)?.uastParameters?.firstIsInstance<KotlinReceiverUParameter>()?.let {
|
(superParent.uastParent as? KotlinUMethod)?.uastParameters?.firstIsInstance<KotlinReceiverUParameter>()?.let {
|
||||||
return it
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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() }}")
|
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
|
@Test
|
||||||
fun testAnonymous() = doTest("Anonymous")
|
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")
|
?: throw AssertionError("requested text '$refText' was not found in $this")
|
||||||
val uElementContainingText = elementAtStart.parentsWithSelf.let {
|
val uElementContainingText = elementAtStart.parentsWithSelf.let {
|
||||||
if (strict) it.dropWhile { !it.text.contains(refText) } else it
|
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) {
|
if (strict && uElementContainingText.psi != null && uElementContainingText.psi?.text != refText) {
|
||||||
throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText")
|
throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user