From 224480ac5e500f069237836164631308a287afa9 Mon Sep 17 00:00:00 2001 From: Nicolay Mitropolsky Date: Fri, 26 Jan 2018 13:25:51 +0300 Subject: [PATCH] 181: UAST: `UAnchorOwner` and type-specific `javaPsi` support --- .../declarations/KotlinUAnnotation.kt.181 | 152 ++++++++++++++++++ .../kotlin/declarations/KotlinUClass.kt.181 | 16 +- .../kotlin/declarations/KotlinUMethod.kt.181 | 11 +- .../declarations/KotlinUVariable.kt.181 | 10 +- .../declarations/UastLightIdentifier.kt.181 | 5 +- .../KotlinUFunctionCallExpression.kt.181 | 5 +- .../testData/SimpleAnnotated.kt.181 | 9 ++ .../tests/KotlinUastApiTest.kt.181 | 1 + 8 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.181 create mode 100644 plugins/uast-kotlin/testData/SimpleAnnotated.kt.181 diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.181 new file mode 100644 index 00000000000..5b7f83dbda5 --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUAnnotation.kt.181 @@ -0,0 +1,152 @@ +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.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.KotlinUIdentifier +import org.jetbrains.uast.kotlin.declarations.KotlinUMethod + +abstract class KotlinUAnnotationBase( + final override val psi: KtElement, + givenParent: UElement? +) : KotlinAbstractUElement(givenParent), UAnnotationEx, UAnchorOwner { + + abstract override val javaPsi: PsiAnnotation? + + final override val sourcePsi = psi + + protected abstract fun annotationUseSiteTarget(): AnnotationUseSiteTarget? + + private val resolvedCall: ResolvedCall<*>? by lz { psi.getResolvedCall(psi.analyze()) } + + override val qualifiedName: String? + get() = annotationClassDescriptor.takeUnless(ErrorUtils::isError) + ?.fqNameUnsafe + ?.takeIf(FqNameUnsafe::isSafe) + ?.toSafe() + ?.toString() + + override val attributeValues: List by lz { + resolvedCall?.valueArguments?.entries?.mapNotNull { + val arguments = it.value.arguments + val name = it.key.name.asString() + when { + arguments.size == 1 -> + KotlinUNamedExpression.create(name, arguments.first(), this) + arguments.size > 1 -> + KotlinUNamedExpression.create(name, arguments, this) + else -> null + } + } ?: emptyList() + } + + protected abstract val annotationClassDescriptor: ClassDescriptor? + + override fun resolve(): PsiClass? { + val descriptor = annotationClassDescriptor ?: return null + return descriptor.toSource()?.getMaybeLightElement(this) as? PsiClass + } + + override fun findAttributeValue(name: String?): UExpression? = + findDeclaredAttributeValue(name) ?: findAttributeDefaultValue(name ?: "value") + + fun findAttributeValueExpression(arg: ValueArgument): UExpression? { + val mapping = resolvedCall?.getArgumentMapping(arg) + return (mapping as? ArgumentMatch)?.let { match -> + val namedExpression = attributeValues.find { it.name == match.valueParameter.name.asString() } + namedExpression?.expression as? KotlinUVarargExpression ?: namedExpression + } + } + + override fun findDeclaredAttributeValue(name: String?): UExpression? { + return attributeValues.find { + it.name == name || + (name == null && it.name == "value") || + (name == "value" && it.name == null) + }?.expression + } + + private fun findAttributeDefaultValue(name: String): UExpression? { + 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) + } + + override fun convertParent(): UElement? { + val superParent = super.convertParent() ?: return null + if (annotationUseSiteTarget() == AnnotationUseSiteTarget.RECEIVER) { + (superParent.uastParent as? KotlinUMethod)?.uastParameters?.firstIsInstance()?.let { + return it + } + } + return superParent + } +} + +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() + + override val uastAnchor by lazy { + KotlinUIdentifier( + javaPsi?.nameReferenceElement?.referenceNameElement, + annotationEntry.typeReference?.typeElement?.let { + (it as? KtUserType)?.referenceExpression?.getReferencedNameElement() ?: it.navigationElement + }, + this + ) + } + +} + +class KotlinUNestedAnnotation( + private val original: KtCallExpression, + givenParent: UElement?, + private val classDescriptor: ClassDescriptor? +) : KotlinUAnnotationBase(original, givenParent) { + override val javaPsi: PsiAnnotation? by lazy { original.toLightAnnotation() } + override val annotationClassDescriptor: ClassDescriptor? + get() = classDescriptor + + override fun annotationUseSiteTarget(): AnnotationUseSiteTarget? = null + + override val uastAnchor by lazy { + KotlinUIdentifier( + javaPsi?.nameReferenceElement?.referenceNameElement, + (original.calleeExpression as? KtNameReferenceExpression)?.getReferencedNameElement(), + this + ) + } + +} + + diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 index 88a3ebf1d12..47a17acf3d6 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUClass.kt.181 @@ -30,7 +30,8 @@ import org.jetbrains.uast.kotlin.declarations.KotlinUIdentifier import org.jetbrains.uast.kotlin.declarations.KotlinUMethod import org.jetbrains.uast.kotlin.declarations.UastLightIdentifier -abstract class AbstractKotlinUClass(givenParent: UElement?) : KotlinAbstractUElement(givenParent), UClass, JvmDeclarationUElement { +abstract class AbstractKotlinUClass(givenParent: UElement?) : KotlinAbstractUElement(givenParent), UClassTypeSpecific, UAnchorOwner, + JvmDeclarationUElement { override val uastDeclarations by lz { mutableListOf().apply { @@ -79,8 +80,7 @@ open class KotlinUClass private constructor( override fun getContainingFile(): PsiFile? = unwrapFakeFileForLightClass(psi.containingFile) - override val uastAnchor: UElement - get() = KotlinUIdentifier(nameIdentifier, ktClass?.nameIdentifier, this) + override val uastAnchor by lazy { KotlinUIdentifier(nameIdentifier, ktClass?.nameIdentifier, this) } override fun getInnerClasses(): Array { // filter DefaultImpls to avoid processing same methods from original interface multiple times @@ -211,10 +211,9 @@ class KotlinUAnonymousClass( override fun getContainingFile(): PsiFile = unwrapFakeFileForLightClass(psi.containingFile) - override val uastAnchor: UElement? - get() { - val ktClassOrObject = (psi.originalElement as? KtLightClass)?.kotlinOrigin as? KtObjectDeclaration ?: return null - return KotlinUIdentifier(ktClassOrObject.getObjectKeyword(), this) + override val uastAnchor by lazy { + val ktClassOrObject = (psi.originalElement as? KtLightClass)?.kotlinOrigin as? KtObjectDeclaration ?: return@lazy null + KotlinUIdentifier(ktClassOrObject.getObjectKeyword(), this) } } @@ -227,8 +226,7 @@ class KotlinScriptUClass( override fun getNameIdentifier(): PsiIdentifier? = UastLightIdentifier(psi, psi.kotlinOrigin) - override val uastAnchor: UElement - get() = KotlinUIdentifier(nameIdentifier, sourcePsi?.nameIdentifier, this) + override val uastAnchor by lazy { KotlinUIdentifier(nameIdentifier, sourcePsi?.nameIdentifier, this) } override val javaPsi: PsiClass = psi diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 index 6f2d663985c..6cc37562c4e 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUMethod.kt.181 @@ -32,7 +32,7 @@ import org.jetbrains.uast.kotlin.* open class KotlinUMethod( psi: KtLightMethod, givenParent: UElement? -) : KotlinAbstractUElement(givenParent), UAnnotationMethod, JavaUElementWithComments, PsiMethod by psi { +) : KotlinAbstractUElement(givenParent), UAnnotationMethod, UMethodTypeSpecific, UAnchorOwner, JavaUElementWithComments, PsiMethod by psi { override val psi: KtLightMethod = unwrap(psi) override val javaPsi = psi @@ -72,12 +72,13 @@ open class KotlinUMethod( uParameters } - override val uastAnchor: UElement - get() = KotlinUIdentifier( + override val uastAnchor by lazy { + KotlinUIdentifier( nameIdentifier, (sourcePsi as? PsiNameIdentifierOwner)?.nameIdentifier ?: sourcePsi?.navigationElement, this ) + } override val uastBody by lz { @@ -108,9 +109,9 @@ open class KotlinUMethod( override val isOverride: Boolean get() = (kotlinOrigin as? KtCallableDeclaration)?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false - override fun getBody(): PsiCodeBlock? = super.getBody() + override fun getBody(): PsiCodeBlock? = super.getBody() - override fun getOriginalElement(): PsiElement? = super.getOriginalElement() + override fun getOriginalElement(): PsiElement? = super.getOriginalElement() override fun equals(other: Any?) = other is KotlinUMethod && psi == other.psi diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 index 40b5bc93901..9c158eba4ce 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/KotlinUVariable.kt.181 @@ -100,7 +100,8 @@ abstract class AbstractKotlinUVariable(givenParent: UElement?) override fun equals(other: Any?) = other is AbstractKotlinUVariable && psi == other.psi - class WrappedUAnnotation(psiAnnotation: PsiAnnotation, override val uastParent: UElement) : UAnnotation, JvmDeclarationUElement { + class WrappedUAnnotation(psiAnnotation: PsiAnnotation, override val uastParent: UElement) : UAnnotation, UAnchorOwner, + JvmDeclarationUElement { override val javaPsi: PsiAnnotation = psiAnnotation override val psi: PsiAnnotation = javaPsi @@ -110,6 +111,8 @@ abstract class AbstractKotlinUVariable(givenParent: UElement?) psi.parameterList.attributes.map { WrappedUNamedExpression(it, this) } } + override val uastAnchor by lazy { KotlinUIdentifier(javaPsi.nameReferenceElement?.referenceNameElement, null, this) } + class WrappedUNamedExpression(pair: PsiNameValuePair, override val uastParent: UElement?) : UNamedExpression, JvmDeclarationUElement { override val name: String? = pair.name override val psi = pair @@ -217,7 +220,8 @@ class KotlinReceiverUParameter( } -class KotlinNullabilityUAnnotation(val annotatedElement: PsiElement, override val uastParent: UElement) : UAnnotation, JvmDeclarationUElement { +class KotlinNullabilityUAnnotation(val annotatedElement: PsiElement, override val uastParent: UElement) : UAnnotationEx, UAnchorOwner, + JvmDeclarationUElement { private fun getTargetType(annotatedElement: PsiElement): KotlinType? { if (annotatedElement is KtTypeReference) { @@ -236,6 +240,8 @@ class KotlinNullabilityUAnnotation(val annotatedElement: PsiElement, override va return null } + override val uastAnchor: UIdentifier? = null + val nullability by lz { getTargetType(annotatedElement)?.nullability() } override val attributeValues: List diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 index b8e1f391445..c59cddabd2a 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/declarations/UastLightIdentifier.kt.181 @@ -18,7 +18,6 @@ package org.jetbrains.uast.kotlin.declarations import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import com.intellij.psi.PsiIdentifier import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier @@ -35,7 +34,7 @@ class UastLightIdentifier(lightOwner: PsiNameIdentifierOwner, ktDeclaration: KtN } class KotlinUIdentifier private constructor( - override val javaPsi: PsiIdentifier?, + override val javaPsi: PsiElement?, override val sourcePsi: PsiElement?, override val psi: PsiElement?, givenParent: UElement? @@ -47,6 +46,6 @@ class KotlinUIdentifier private constructor( override val uastParent: UElement? by lazy { givenParent ?: sourcePsi?.parent?.toUElement() } - constructor(javaPsi: PsiIdentifier?, sourcePsi: PsiElement?, uastParent: UElement?) : this(javaPsi, sourcePsi, javaPsi, uastParent) + constructor(javaPsi: PsiElement?, sourcePsi: PsiElement?, uastParent: UElement?) : this(javaPsi, sourcePsi, javaPsi, uastParent) constructor(sourcePsi: PsiElement?, uastParent: UElement?) : this(null, sourcePsi, sourcePsi, uastParent) } \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 index a78101c95bc..df9b741beda 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/expressions/KotlinUFunctionCallExpression.kt.181 @@ -101,7 +101,7 @@ class KotlinUFunctionCallExpression( } private fun createVarargsHolder(arguments: List, parent: UElement?): KotlinUExpressionList = - KotlinUExpressionList(null, VARARGS, parent).apply { + KotlinUExpressionList(null, UastSpecialExpressionKind.VARARGS, parent).apply { expressions = arguments.map { KotlinConverter.convertOrEmpty(it.getArgumentExpression(), parent) } } @@ -160,6 +160,3 @@ class KotlinUFunctionCallExpression( } } - -@Deprecated("will be replaced by one from uast api when it comes") -val VARARGS = UastSpecialExpressionKind("varargs") \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/SimpleAnnotated.kt.181 b/plugins/uast-kotlin/testData/SimpleAnnotated.kt.181 new file mode 100644 index 00000000000..eba544fa27c --- /dev/null +++ b/plugins/uast-kotlin/testData/SimpleAnnotated.kt.181 @@ -0,0 +1,9 @@ +class SimpleAnnotated { + @Suppress("abc") + fun method() { + println("Hello, world!") + } + + @kotlin.SinceKotlin("1.0") + val property: String = "Mary" +} \ No newline at end of file diff --git a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 index 07db0b84610..75b0be02573 100644 --- a/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 +++ b/plugins/uast-kotlin/tests/KotlinUastApiTest.kt.181 @@ -304,6 +304,7 @@ class KotlinUastApiTest : AbstractKotlinUastTest() { val lightAnnotation = convertedUAnnotation.getAsJavaPsiElement(PsiAnnotation::class.java) ?: throw AssertionError("can't get lightAnnotation from $convertedUAnnotation") assertEquals("Annotation", lightAnnotation.qualifiedName) + assertEquals("Annotation", (convertedUAnnotation as UAnchorOwner).uastAnchor?.sourcePsi?.text) } }