Making LightAnnotation work without clsDelegate (KT-20924, KT-22883)
This commit is contained in:
committed by
xiexed
parent
03e46ce0ca
commit
eea66bea73
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.LanguageConstantExpressionEvaluator
|
||||
import com.intellij.psi.impl.light.LightIdentifier
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
|
||||
class KtLightPsiArrayInitializerMemberValue(
|
||||
override val kotlinOrigin: KtElement,
|
||||
val lightParent: PsiElement,
|
||||
val arguments: (KtLightPsiArrayInitializerMemberValue) -> List<PsiAnnotationMemberValue>
|
||||
) : KtLightElementBase(lightParent), PsiArrayInitializerMemberValue {
|
||||
override fun getInitializers(): Array<PsiAnnotationMemberValue> = arguments(this).toTypedArray()
|
||||
|
||||
override fun getParent(): PsiElement = lightParent
|
||||
|
||||
override fun isPhysical(): Boolean = false
|
||||
}
|
||||
|
||||
class KtLightPsiLiteral(
|
||||
override val kotlinOrigin: KtExpression,
|
||||
val lightParent: PsiElement
|
||||
) : KtLightElementBase(lightParent), PsiLiteralExpression {
|
||||
|
||||
override fun getValue(): Any? =
|
||||
LanguageConstantExpressionEvaluator.INSTANCE.forLanguage(kotlinOrigin.language)?.computeConstantExpression(this, false)
|
||||
|
||||
override fun getType(): PsiType? {
|
||||
val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin)
|
||||
val kotlinType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, kotlinOrigin] ?: return null
|
||||
val typeFqName = kotlinType.constructor.declarationDescriptor?.fqNameSafe?.asString() ?: return null
|
||||
return when (typeFqName) {
|
||||
"kotlin.Int" -> PsiType.INT
|
||||
"kotlin.Long" -> PsiType.LONG
|
||||
"kotlin.Short" -> PsiType.SHORT
|
||||
"kotlin.Boolean" -> PsiType.BOOLEAN
|
||||
"kotlin.Byte" -> PsiType.BYTE
|
||||
"kotlin.Char" -> PsiType.CHAR
|
||||
"kotlin.Double" -> PsiType.DOUBLE
|
||||
"kotlin.Float" -> PsiType.FLOAT
|
||||
"kotlin.String" -> PsiType.getJavaLangString(kotlinOrigin.manager, GlobalSearchScope.projectScope(kotlinOrigin.project))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getParent(): PsiElement = lightParent
|
||||
|
||||
override fun isPhysical(): Boolean = false
|
||||
|
||||
override fun replace(newElement: PsiElement): PsiElement {
|
||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||
kotlinOrigin.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\""))
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getReference(): PsiReference? = references.singleOrNull()
|
||||
override fun getReferences(): Array<out PsiReference> = kotlinOrigin.references
|
||||
}
|
||||
|
||||
class KtLightPsiNameValuePair private constructor(
|
||||
override val kotlinOrigin: KtElement,
|
||||
val valueArgument: KtValueArgument,
|
||||
lightParent: PsiElement
|
||||
) : KtLightElementBase(lightParent),
|
||||
PsiNameValuePair {
|
||||
|
||||
constructor(valueArgument: KtValueArgument, lightParent: PsiElement) : this(valueArgument.asElement(), valueArgument, lightParent)
|
||||
|
||||
override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue =
|
||||
throw UnsupportedOperationException("can't modify KtLightPsiNameValuePair")
|
||||
|
||||
override fun getNameIdentifier(): PsiIdentifier? = LightIdentifier(kotlinOrigin.manager, valueArgument.name)
|
||||
|
||||
override fun getName(): String? = valueArgument.getArgumentName()?.asName?.asString()
|
||||
|
||||
override fun getValue(): PsiAnnotationMemberValue? =
|
||||
valueArgument.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(this, it) }
|
||||
|
||||
override fun getLiteralValue(): String? = (getValue() as? PsiLiteralExpression)?.value?.toString()
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.LanguageConstantExpressionEvaluator
|
||||
import com.intellij.psi.impl.light.LightIdentifier
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
|
||||
class KtLightPsiArrayInitializerMemberValue(
|
||||
override val kotlinOrigin: KtElement,
|
||||
val lightParent: PsiElement,
|
||||
val arguments: (KtLightPsiArrayInitializerMemberValue) -> List<PsiAnnotationMemberValue>
|
||||
) : KtLightElementBase(lightParent), PsiArrayInitializerMemberValue {
|
||||
override fun getInitializers(): Array<PsiAnnotationMemberValue> = arguments(this).toTypedArray()
|
||||
|
||||
override fun getParent(): PsiElement = lightParent
|
||||
|
||||
override fun isPhysical(): Boolean = true
|
||||
}
|
||||
|
||||
class KtLightPsiLiteral(
|
||||
override val kotlinOrigin: KtExpression,
|
||||
val lightParent: PsiElement
|
||||
) : KtLightElementBase(lightParent), PsiLiteralExpression {
|
||||
|
||||
override fun getValue(): Any? =
|
||||
LanguageConstantExpressionEvaluator.INSTANCE.forLanguage(kotlinOrigin.language)?.computeConstantExpression(this, false)
|
||||
|
||||
override fun getType(): PsiType? {
|
||||
val bindingContext = LightClassGenerationSupport.getInstance(this.project).analyze(kotlinOrigin)
|
||||
val kotlinType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, kotlinOrigin] ?: return null
|
||||
val typeFqName = kotlinType.constructor.declarationDescriptor?.fqNameSafe?.asString() ?: return null
|
||||
return when (typeFqName) {
|
||||
"kotlin.Int" -> PsiType.INT
|
||||
"kotlin.Long" -> PsiType.LONG
|
||||
"kotlin.Short" -> PsiType.SHORT
|
||||
"kotlin.Boolean" -> PsiType.BOOLEAN
|
||||
"kotlin.Byte" -> PsiType.BYTE
|
||||
"kotlin.Char" -> PsiType.CHAR
|
||||
"kotlin.Double" -> PsiType.DOUBLE
|
||||
"kotlin.Float" -> PsiType.FLOAT
|
||||
"kotlin.String" -> PsiType.getJavaLangString(kotlinOrigin.manager, GlobalSearchScope.projectScope(kotlinOrigin.project))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getParent(): PsiElement = lightParent
|
||||
|
||||
override fun isPhysical(): Boolean = true
|
||||
|
||||
override fun replace(newElement: PsiElement): PsiElement {
|
||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||
kotlinOrigin.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\""))
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getReference(): PsiReference? = references.singleOrNull()
|
||||
override fun getReferences(): Array<out PsiReference> = kotlinOrigin.references
|
||||
}
|
||||
|
||||
class KtLightPsiNameValuePair private constructor(
|
||||
override val kotlinOrigin: KtElement,
|
||||
val valueArgument: KtValueArgument,
|
||||
lightParent: PsiElement
|
||||
) : KtLightElementBase(lightParent),
|
||||
PsiNameValuePair {
|
||||
|
||||
constructor(valueArgument: KtValueArgument, lightParent: PsiElement) : this(valueArgument.asElement(), valueArgument, lightParent)
|
||||
|
||||
override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue =
|
||||
throw UnsupportedOperationException("can't modify KtLightPsiNameValuePair")
|
||||
|
||||
override fun getNameIdentifier(): PsiIdentifier? = LightIdentifier(kotlinOrigin.manager, valueArgument.name)
|
||||
|
||||
override fun getName(): String? = valueArgument.getArgumentName()?.asName?.asString()
|
||||
|
||||
override fun getValue(): PsiAnnotationMemberValue? =
|
||||
valueArgument.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(this, it) }
|
||||
|
||||
override fun getLiteralValue(): String? = (getValue() as? PsiLiteralExpression)?.value?.toString()
|
||||
|
||||
}
|
||||
+2
-2
@@ -27,8 +27,8 @@ import org.jetbrains.kotlin.psi.KtSecondaryConstructor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
open class KtLightIdentifier(
|
||||
private val lightOwner: PsiNameIdentifierOwner,
|
||||
private val ktDeclaration: KtNamedDeclaration?
|
||||
private val lightOwner: PsiElement,
|
||||
private val ktDeclaration: KtNamedDeclaration?
|
||||
) : LightIdentifier(lightOwner.manager, ktDeclaration?.name ?: ""), PsiCompiledElement {
|
||||
val origin: PsiElement?
|
||||
get() = when (ktDeclaration) {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.scope.PsiScopeProcessor
|
||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||
|
||||
class KtLightPsiJavaCodeReferenceElement(
|
||||
private val ktElement: PsiElement,
|
||||
private val reference: PsiReference,
|
||||
private val clsDelegateProvider: () -> PsiJavaCodeReferenceElement
|
||||
) :
|
||||
PsiElement by ktElement,
|
||||
PsiReference by reference,
|
||||
PsiJavaCodeReferenceElement {
|
||||
|
||||
private val delegate by lazyPub { clsDelegateProvider() }
|
||||
|
||||
override fun advancedResolve(incompleteCode: Boolean): JavaResolveResult = delegate.advancedResolve(incompleteCode)
|
||||
|
||||
override fun getReferenceNameElement(): PsiElement? = ktElement
|
||||
|
||||
override fun getTypeParameters(): Array<PsiType> = delegate.typeParameters
|
||||
|
||||
override fun getReferenceName(): String? = delegate.referenceName
|
||||
|
||||
override fun isQualified(): Boolean = delegate.isQualified
|
||||
|
||||
override fun processVariants(processor: PsiScopeProcessor) = delegate.processVariants(processor)
|
||||
|
||||
override fun multiResolve(incompleteCode: Boolean): Array<JavaResolveResult> = delegate.multiResolve(incompleteCode)
|
||||
|
||||
override fun getQualifiedName(): String = delegate.qualifiedName
|
||||
|
||||
override fun getQualifier(): PsiElement? = delegate.qualifier
|
||||
|
||||
override fun getParameterList(): PsiReferenceParameterList? = delegate.parameterList
|
||||
}
|
||||
+136
-212
@@ -16,36 +16,41 @@
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.cannotModify
|
||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations")
|
||||
|
||||
abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) :
|
||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||
override val clsDelegate by lazyPub(computeDelegate)
|
||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||
|
||||
private val _clsDelegate: PsiAnnotation by lazyPub(computeDelegate)
|
||||
|
||||
override val clsDelegate: PsiAnnotation
|
||||
get() {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode && this !is KtLightNonSourceAnnotation)
|
||||
LOG.error("KtLightAbstractAnnotation clsDelegate requested for ${this.javaClass}")
|
||||
return _clsDelegate
|
||||
}
|
||||
|
||||
override fun getNameReferenceElement() = clsDelegate.nameReferenceElement
|
||||
|
||||
@@ -64,8 +69,6 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: ()
|
||||
open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName
|
||||
}
|
||||
|
||||
private typealias AnnotationValueOrigin = () -> PsiElement?
|
||||
|
||||
class KtLightAnnotationForSourceEntry(
|
||||
private val qualifiedName: String,
|
||||
override val kotlinOrigin: KtCallElement,
|
||||
@@ -75,208 +78,78 @@ class KtLightAnnotationForSourceEntry(
|
||||
|
||||
override fun getQualifiedName() = qualifiedName
|
||||
|
||||
open inner class LightElementValue<out D : PsiElement>(
|
||||
val delegate: D,
|
||||
private val parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate {
|
||||
override fun getMirror(): PsiElement = delegate
|
||||
override fun getName(): String? = null
|
||||
|
||||
val originalExpression: PsiElement? by lazyPub(valueOrigin)
|
||||
override fun findAttributeValue(name: String?) = getAttributeValue(name, true)
|
||||
|
||||
fun getConstantValue(): Any? {
|
||||
val expression = originalExpression as? KtExpression ?: return null
|
||||
val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin
|
||||
val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry)
|
||||
return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE)
|
||||
}
|
||||
override fun findDeclaredAttributeValue(name: String?): PsiAnnotationMemberValue? = getAttributeValue(name, false)
|
||||
|
||||
override fun getReference() = references.singleOrNull()
|
||||
override fun getReferences() = originalExpression?.references.orEmpty()
|
||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
||||
override fun getNavigationElement() = originalExpression
|
||||
override fun isPhysical(): Boolean = false
|
||||
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
||||
override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0
|
||||
override fun getParent() = parent
|
||||
override fun getText() = originalExpression?.text.orEmpty()
|
||||
override fun getContainingFile(): PsiFile? = if (originalExpression?.containingFile == kotlinOrigin.containingFile)
|
||||
kotlinOrigin.containingFile else delegate.containingFile
|
||||
private fun getAttributeValue(name: String?, useDefault: Boolean): PsiAnnotationMemberValue? {
|
||||
val name = name ?: "value"
|
||||
|
||||
override fun replace(newElement: PsiElement): PsiElement {
|
||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||
val origin = originalExpression
|
||||
val resolvedCall = kotlinOrigin.getResolvedCall() ?: return null
|
||||
val callEntry = resolvedCall.valueArguments.entries.find { (param, _) -> param.name.asString() == name } ?: return null
|
||||
|
||||
val exprToReplace =
|
||||
if (origin is KtCallExpression /*arrayOf*/) {
|
||||
unwrapArray(origin.valueArguments)
|
||||
}
|
||||
else {
|
||||
origin as? KtExpression
|
||||
} ?: return this
|
||||
exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\""))
|
||||
val valueArguments = callEntry.value.arguments
|
||||
val valueArgument = valueArguments.firstOrNull()
|
||||
val argument = valueArgument?.getArgumentExpression()
|
||||
if (argument != null) {
|
||||
val arrayExpected = callEntry.key?.type?.let { KotlinBuiltIns.isArray(it) } ?: false
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? {
|
||||
val resolvedCall = callHolder.getResolvedCall() ?: return null
|
||||
val annotationConstructor = resolvedCall.resultingDescriptor
|
||||
val parameterName =
|
||||
memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?:
|
||||
memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?:
|
||||
"value"
|
||||
|
||||
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null
|
||||
val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null
|
||||
return when (resolvedArgument) {
|
||||
is DefaultValueArgument -> {
|
||||
val psi = parameter.source.getPsi()
|
||||
when (psi) {
|
||||
is KtParameter -> psi.defaultValue
|
||||
is PsiAnnotationMethod -> psi.defaultValue
|
||||
else -> error("$psi of type ${psi?.javaClass}")
|
||||
}
|
||||
}
|
||||
|
||||
is ExpressionValueArgument -> {
|
||||
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
||||
argExpression?.asKtCall()
|
||||
?: argExpression
|
||||
?: error("resolvedArgument ($resolvedArgument) has no arg expression")
|
||||
}
|
||||
|
||||
is VarargValueArgument ->
|
||||
memberValue.unwrapArray(resolvedArgument.arguments)
|
||||
?: resolvedArgument.arguments.first().asElement().let {
|
||||
(it as? KtValueArgument)
|
||||
?.takeIf {
|
||||
it.getSpreadElement() != null ||
|
||||
it.getArgumentName() != null ||
|
||||
it.getArgumentExpression() is KtCollectionLiteralExpression
|
||||
}
|
||||
?.getArgumentExpression() ?: it.parent
|
||||
}
|
||||
|
||||
else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.unwrapArray(arguments: List<ValueArgument>): PsiElement? {
|
||||
val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null
|
||||
val exprIndex = arrayInitializer.initializers.indexOf(this)
|
||||
if (exprIndex < 0 || exprIndex >= arguments.size) return null
|
||||
return arguments[exprIndex].getArgumentExpression()
|
||||
}
|
||||
|
||||
open inner class LightExpressionValue<out D : PsiExpression>(
|
||||
delegate: D,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<D>(delegate, parent, valueOrigin), PsiExpression {
|
||||
override fun getType(): PsiType? = delegate.type
|
||||
}
|
||||
|
||||
inner class LightPsiLiteral(
|
||||
delegate: PsiLiteralExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, valueOrigin), PsiLiteralExpression {
|
||||
override fun getValue() = delegate.value
|
||||
}
|
||||
|
||||
inner class LightClassLiteral(
|
||||
delegate: PsiClassObjectAccessExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, valueOrigin), PsiClassObjectAccessExpression {
|
||||
override fun getType() = delegate.type
|
||||
override fun getOperand(): PsiTypeElement = delegate.operand
|
||||
}
|
||||
|
||||
inner class LightArrayInitializerValue(
|
||||
delegate: PsiArrayInitializerMemberValue,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue {
|
||||
private val _initializers by lazyPub {
|
||||
delegate.initializers.mapIndexed { i, memberValue ->
|
||||
wrapAnnotationValue(memberValue, this, {
|
||||
originalExpression.let { ktOrigin ->
|
||||
when {
|
||||
ktOrigin is KtCallExpression
|
||||
&& memberValue is PsiAnnotation
|
||||
&& isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin
|
||||
ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i)
|
||||
delegate.initializers.size == 1 -> ktOrigin
|
||||
else -> null
|
||||
}.also {
|
||||
if (it == null)
|
||||
LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}",
|
||||
Attachment(
|
||||
"source_fragments.txt",
|
||||
"origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}"
|
||||
)
|
||||
)
|
||||
if (arrayExpected && (argument is KtStringTemplateExpression || argument is KtConstantExpression || getAnnotationName(argument) != null))
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
PsiTreeUtil.findCommonParent(valueArguments.map { it.getArgumentExpression() }) as KtElement,
|
||||
this,
|
||||
{ self ->
|
||||
valueArguments.mapNotNull {
|
||||
it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}.toTypedArray()
|
||||
})
|
||||
|
||||
ktLightAnnotationParameterList.attributes.find { (it as KtLightPsiNameValuePair).valueArgument === valueArgument }?.let {
|
||||
return it.value
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInitializers() = _initializers
|
||||
if (useDefault && callEntry.key.declaresOrInheritsDefaultValue()) {
|
||||
val psiElement = callEntry.key.source.getPsi()
|
||||
when (psiElement) {
|
||||
is KtParameter ->
|
||||
return psiElement.defaultValue?.let { convertToLightAnnotationMemberValue(this, it) }
|
||||
is PsiAnnotationMethod ->
|
||||
return psiElement.defaultValue
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue =
|
||||
when {
|
||||
value is PsiLiteralExpression -> LightPsiLiteral(value, parent, ktOrigin)
|
||||
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin)
|
||||
value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin)
|
||||
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin)
|
||||
value is PsiAnnotation -> {
|
||||
val origin = ktOrigin()
|
||||
val ktCallElement = origin?.asKtCall()
|
||||
val qualifiedName = value.qualifiedName
|
||||
if (qualifiedName != null && ktCallElement != null)
|
||||
KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value })
|
||||
else {
|
||||
LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})",
|
||||
Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'"))
|
||||
LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead?
|
||||
}
|
||||
}
|
||||
else -> LightElementValue(value, parent, ktOrigin)
|
||||
}
|
||||
|
||||
override fun getName() = null
|
||||
override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? {
|
||||
val reference = (kotlinOrigin as? KtAnnotationEntry)?.typeReference?.reference
|
||||
?: (kotlinOrigin.calleeExpression as? KtNameReferenceExpression)?.reference
|
||||
?: return null
|
||||
return KtLightPsiJavaCodeReferenceElement(
|
||||
kotlinOrigin.navigationElement,
|
||||
reference,
|
||||
{ super.getNameReferenceElement()!! })
|
||||
}
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, {
|
||||
getMemberValueAsCallArgument(value, kotlinOrigin)
|
||||
})
|
||||
|
||||
override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
private val ktLightAnnotationParameterList by lazyPub { KtLightAnnotationParameterList() }
|
||||
|
||||
override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
override fun getParameterList(): PsiAnnotationParameterList = ktLightAnnotationParameterList
|
||||
|
||||
override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList())
|
||||
|
||||
inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this),
|
||||
inner class KtLightAnnotationParameterList() : KtLightElementBase(this),
|
||||
PsiAnnotationParameterList {
|
||||
override val kotlinOrigin get() = null
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray()
|
||||
|
||||
inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this),
|
||||
PsiNameValuePair {
|
||||
override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue)
|
||||
override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier
|
||||
override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString()
|
||||
override val kotlinOrigin: KtElement? = null
|
||||
|
||||
override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) }
|
||||
private val _attributes: Array<PsiNameValuePair> by lazyPub {
|
||||
this@KtLightAnnotationForSourceEntry.kotlinOrigin.valueArguments.map { KtLightPsiNameValuePair(it as KtValueArgument, this) }
|
||||
.toTypedArray<PsiNameValuePair>()
|
||||
}
|
||||
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = _attributes
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -296,10 +169,10 @@ class KtLightAnnotationForSourceEntry(
|
||||
}
|
||||
|
||||
class KtLightNonSourceAnnotation(
|
||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||
) : KtLightAbstractAnnotation(parent, { clsDelegate }) {
|
||||
override val kotlinOrigin: KtAnnotationEntry? get() = null
|
||||
override fun getQualifiedName() = clsDelegate.qualifiedName
|
||||
override fun getQualifiedName() = kotlinOrigin?.name ?: clsDelegate.qualifiedName
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName)
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName)
|
||||
@@ -348,7 +221,9 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne
|
||||
|
||||
override fun findAttributeValue(attributeName: String?) = null
|
||||
|
||||
override fun getQualifiedName(): String? = clsDelegate.qualifiedName
|
||||
override fun getQualifiedName(): String? = Nullable::class.java.name
|
||||
|
||||
override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? = null
|
||||
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = null
|
||||
}
|
||||
@@ -362,18 +237,67 @@ private fun KtElement.getResolvedCall(): ResolvedCall<out CallableDescriptor>? {
|
||||
return this.getResolvedCall(context)
|
||||
}
|
||||
|
||||
private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) =
|
||||
(callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName
|
||||
|
||||
private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement
|
||||
|
||||
private fun psiReport(psiElement: PsiElement?): String {
|
||||
if (psiElement == null) return "null"
|
||||
val text = try {
|
||||
psiElement.text
|
||||
fun convertToLightAnnotationMemberValue(lightParent: PsiElement, argument: KtExpression): PsiAnnotationMemberValue {
|
||||
val argument = unwrapCall(argument)
|
||||
when (argument) {
|
||||
is KtStringTemplateExpression, is KtConstantExpression -> {
|
||||
return KtLightPsiLiteral(argument, lightParent)
|
||||
}
|
||||
is KtCallExpression -> {
|
||||
val arguments = argument.valueArguments
|
||||
val annotationName = argument.calleeExpression?.let { getAnnotationName(it) }
|
||||
if (annotationName != null) {
|
||||
return KtLightAnnotationForSourceEntry(
|
||||
annotationName,
|
||||
argument,
|
||||
lightParent,
|
||||
{ throw UnsupportedOperationException("cls delegate is not supported for nested annotations") })
|
||||
}
|
||||
val resolvedCall = argument.getResolvedCall()
|
||||
if (resolvedCall != null && CompileTimeConstantUtils.isArrayFunctionCall(resolvedCall))
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
argument,
|
||||
lightParent,
|
||||
{ self ->
|
||||
arguments.mapNotNull {
|
||||
it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) }
|
||||
}
|
||||
})
|
||||
}
|
||||
is KtCollectionLiteralExpression -> {
|
||||
val arguments = argument.getInnerExpressions()
|
||||
if (arguments.isNotEmpty())
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
argument,
|
||||
lightParent,
|
||||
{ self -> arguments.mapNotNull { convertToLightAnnotationMemberValue(self, it) } })
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
"${e.javaClass.simpleName}:${e.message}"
|
||||
// everything else (like complex constant references) considered as PsiLiteral-s
|
||||
return KtLightPsiLiteral(argument, lightParent)
|
||||
}
|
||||
|
||||
private val KtExpression.nameReference: KtNameReferenceExpression?
|
||||
get() = when {
|
||||
this is KtConstructorCalleeExpression -> constructorReferenceExpression as? KtNameReferenceExpression
|
||||
else -> this as? KtNameReferenceExpression
|
||||
}
|
||||
return "${psiElement.javaClass.canonicalName}[$text]"
|
||||
|
||||
private fun unwrapCall(callee: KtExpression): KtExpression = when (callee) {
|
||||
is KtDotQualifiedExpression -> callee.lastChild as? KtCallExpression ?: callee
|
||||
else -> callee
|
||||
}
|
||||
|
||||
private fun getAnnotationName(callee: KtExpression): String? {
|
||||
val callee = unwrapCall(callee)
|
||||
val resultingDescriptor = callee.getResolvedCall()?.resultingDescriptor
|
||||
if (resultingDescriptor is ClassConstructorDescriptor) {
|
||||
val ktClass = resultingDescriptor.constructedClass.source.getPsi() as? KtClass
|
||||
if (ktClass?.isAnnotation() == true) return ktClass.fqName?.toString()
|
||||
}
|
||||
if (resultingDescriptor is JavaClassConstructorDescriptor) {
|
||||
val psiClass = resultingDescriptor.constructedClass.source.getPsi() as? PsiClass
|
||||
if (psiClass?.isAnnotationType == true) return psiClass.qualifiedName
|
||||
}
|
||||
return null
|
||||
}
|
||||
-368
@@ -1,368 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.cannotModify
|
||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations")
|
||||
|
||||
abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) :
|
||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||
override val clsDelegate by lazyPub(computeDelegate)
|
||||
|
||||
override fun getNameReferenceElement() = clsDelegate.nameReferenceElement
|
||||
|
||||
override fun getOwner() = parent as? PsiAnnotationOwner
|
||||
|
||||
override fun getMetaData() = clsDelegate.metaData
|
||||
|
||||
override fun getParameterList() = clsDelegate.parameterList
|
||||
|
||||
open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName
|
||||
}
|
||||
|
||||
private typealias AnnotationValueOrigin = () -> PsiElement?
|
||||
|
||||
class KtLightAnnotationForSourceEntry(
|
||||
private val qualifiedName: String,
|
||||
override val kotlinOrigin: KtCallElement,
|
||||
parent: PsiElement,
|
||||
computeDelegate: () -> PsiAnnotation
|
||||
) : KtLightAbstractAnnotation(parent, computeDelegate) {
|
||||
|
||||
override fun getQualifiedName() = qualifiedName
|
||||
|
||||
open inner class LightElementValue<out D : PsiElement>(
|
||||
val delegate: D,
|
||||
private val parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate {
|
||||
override fun getMirror(): PsiElement = delegate
|
||||
|
||||
val originalExpression: PsiElement? by lazyPub(valueOrigin)
|
||||
|
||||
fun getConstantValue(): Any? {
|
||||
val expression = originalExpression as? KtExpression ?: return null
|
||||
val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin
|
||||
val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry)
|
||||
return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE)
|
||||
}
|
||||
|
||||
override fun getReference() = references.singleOrNull()
|
||||
override fun getReferences() = originalExpression?.references.orEmpty()
|
||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
||||
override fun getNavigationElement() = originalExpression
|
||||
override fun isPhysical(): Boolean = originalExpression?.containingFile == kotlinOrigin.containingFile
|
||||
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
||||
override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0
|
||||
override fun getParent() = parent
|
||||
override fun getText() = originalExpression?.text.orEmpty()
|
||||
override fun getContainingFile(): PsiFile? = if (isPhysical) kotlinOrigin.containingFile else delegate.containingFile
|
||||
|
||||
override fun replace(newElement: PsiElement): PsiElement {
|
||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||
val origin = originalExpression
|
||||
|
||||
val exprToReplace =
|
||||
if (origin is KtCallExpression /*arrayOf*/) {
|
||||
unwrapArray(origin.valueArguments)
|
||||
}
|
||||
else {
|
||||
origin as? KtExpression
|
||||
} ?: return this
|
||||
exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\""))
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? {
|
||||
val resolvedCall = callHolder.getResolvedCall() ?: return null
|
||||
val annotationConstructor = resolvedCall.resultingDescriptor
|
||||
val parameterName =
|
||||
memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?:
|
||||
memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?:
|
||||
"value"
|
||||
|
||||
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null
|
||||
val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null
|
||||
return when (resolvedArgument) {
|
||||
is DefaultValueArgument -> {
|
||||
val psi = parameter.source.getPsi()
|
||||
when (psi) {
|
||||
is KtParameter -> psi.defaultValue
|
||||
is PsiAnnotationMethod -> psi.defaultValue
|
||||
else -> error("$psi of type ${psi?.javaClass}")
|
||||
}
|
||||
}
|
||||
|
||||
is ExpressionValueArgument -> {
|
||||
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
||||
argExpression?.asKtCall()
|
||||
?: argExpression
|
||||
?: error("resolvedArgument ($resolvedArgument) has no arg expression")
|
||||
}
|
||||
|
||||
is VarargValueArgument ->
|
||||
memberValue.unwrapArray(resolvedArgument.arguments)
|
||||
?: resolvedArgument.arguments.first().asElement().let {
|
||||
(it as? KtValueArgument)
|
||||
?.takeIf {
|
||||
it.getSpreadElement() != null ||
|
||||
it.getArgumentName() != null ||
|
||||
it.getArgumentExpression() is KtCollectionLiteralExpression
|
||||
}
|
||||
?.getArgumentExpression() ?: it.parent
|
||||
}
|
||||
|
||||
else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.unwrapArray(arguments: List<ValueArgument>): PsiElement? {
|
||||
val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null
|
||||
val exprIndex = arrayInitializer.initializers.indexOf(this)
|
||||
if (exprIndex < 0 || exprIndex >= arguments.size) return null
|
||||
return arguments[exprIndex].getArgumentExpression()
|
||||
}
|
||||
|
||||
open inner class LightExpressionValue<out D : PsiExpression>(
|
||||
delegate: D,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<D>(delegate, parent, valueOrigin), PsiExpression {
|
||||
override fun getType(): PsiType? = delegate.type
|
||||
}
|
||||
|
||||
inner class LightPsiLiteral(
|
||||
delegate: PsiLiteralExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, valueOrigin), PsiLiteralExpression {
|
||||
override fun getValue() = delegate.value
|
||||
}
|
||||
|
||||
inner class LightClassLiteral(
|
||||
delegate: PsiClassObjectAccessExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, valueOrigin), PsiClassObjectAccessExpression {
|
||||
override fun getType() = delegate.type
|
||||
override fun getOperand(): PsiTypeElement = delegate.operand
|
||||
}
|
||||
|
||||
inner class LightArrayInitializerValue(
|
||||
delegate: PsiArrayInitializerMemberValue,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue {
|
||||
private val _initializers by lazyPub {
|
||||
delegate.initializers.mapIndexed { i, memberValue ->
|
||||
wrapAnnotationValue(memberValue, this, {
|
||||
originalExpression.let { ktOrigin ->
|
||||
when {
|
||||
ktOrigin is KtCallExpression
|
||||
&& memberValue is PsiAnnotation
|
||||
&& isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin
|
||||
ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i)
|
||||
delegate.initializers.size == 1 -> ktOrigin
|
||||
else -> null
|
||||
}.also {
|
||||
if (it == null)
|
||||
LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}",
|
||||
Attachment(
|
||||
"source_fragments.txt",
|
||||
"origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
override fun getInitializers() = _initializers
|
||||
}
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue =
|
||||
when {
|
||||
value is PsiLiteralExpression -> LightPsiLiteral(value, parent, ktOrigin)
|
||||
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin)
|
||||
value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin)
|
||||
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin)
|
||||
value is PsiAnnotation -> {
|
||||
val origin = ktOrigin()
|
||||
val ktCallElement = origin?.asKtCall()
|
||||
val qualifiedName = value.qualifiedName
|
||||
if (qualifiedName != null && ktCallElement != null)
|
||||
KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value })
|
||||
else {
|
||||
LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})",
|
||||
Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'"))
|
||||
LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead?
|
||||
}
|
||||
}
|
||||
else -> LightElementValue(value, parent, ktOrigin)
|
||||
}
|
||||
|
||||
override fun isPhysical() = true
|
||||
|
||||
override fun getName() = null
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, {
|
||||
getMemberValueAsCallArgument(value, kotlinOrigin)
|
||||
})
|
||||
|
||||
override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
|
||||
override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
|
||||
override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList())
|
||||
|
||||
inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this),
|
||||
PsiAnnotationParameterList {
|
||||
override val kotlinOrigin get() = null
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray()
|
||||
|
||||
inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this),
|
||||
PsiNameValuePair {
|
||||
override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue)
|
||||
override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier
|
||||
override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString()
|
||||
override val kotlinOrigin: KtElement? = null
|
||||
|
||||
override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun delete() = kotlinOrigin.delete()
|
||||
|
||||
override fun toString() = "@$qualifiedName"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || other::class.java != this::class.java) return false
|
||||
return kotlinOrigin == (other as KtLightAnnotationForSourceEntry).kotlinOrigin
|
||||
}
|
||||
|
||||
override fun hashCode() = kotlinOrigin.hashCode()
|
||||
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
}
|
||||
|
||||
class KtLightNonSourceAnnotation(
|
||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||
) : KtLightAbstractAnnotation(parent, { clsDelegate }) {
|
||||
override val kotlinOrigin: KtAnnotationEntry? get() = null
|
||||
override fun getQualifiedName() = clsDelegate.qualifiedName
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName)
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName)
|
||||
}
|
||||
|
||||
class KtLightNonExistentAnnotation(parent: KtLightElement<*, *>) : KtLightElementBase(parent), PsiAnnotation {
|
||||
override val kotlinOrigin get() = null
|
||||
override fun toString() = this.javaClass.name
|
||||
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
|
||||
override fun getNameReferenceElement() = null
|
||||
override fun findAttributeValue(attributeName: String?) = null
|
||||
override fun getQualifiedName() = null
|
||||
override fun getOwner() = parent as? PsiAnnotationOwner
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = null
|
||||
override fun getMetaData() = null
|
||||
override fun getParameterList() = KtLightEmptyAnnotationParameterList(this)
|
||||
}
|
||||
|
||||
class KtLightEmptyAnnotationParameterList(parent: PsiElement) : KtLightElementBase(parent), PsiAnnotationParameterList {
|
||||
override val kotlinOrigin get() = null
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = emptyArray()
|
||||
}
|
||||
|
||||
class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwner>, parent: PsiElement) : KtLightAbstractAnnotation(parent, {
|
||||
// searching for last because nullability annotations are generated after backend generates source annotations
|
||||
member.clsDelegate.modifierList?.annotations?.findLast {
|
||||
isNullabilityAnnotation(it.qualifiedName)
|
||||
} ?: KtLightNonExistentAnnotation(member)
|
||||
}) {
|
||||
override fun fqNameMatches(fqName: String): Boolean {
|
||||
if (!isNullabilityAnnotation(fqName)) return false
|
||||
|
||||
return super.fqNameMatches(fqName)
|
||||
}
|
||||
|
||||
override val kotlinOrigin get() = null
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
|
||||
override fun findAttributeValue(attributeName: String?) = null
|
||||
|
||||
override fun getQualifiedName(): String? = clsDelegate.qualifiedName
|
||||
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = null
|
||||
}
|
||||
|
||||
internal fun isNullabilityAnnotation(qualifiedName: String?) = qualifiedName in backendNullabilityAnnotations
|
||||
|
||||
private val backendNullabilityAnnotations = arrayOf(Nullable::class.java.name, NotNull::class.java.name)
|
||||
|
||||
private fun KtElement.getResolvedCall(): ResolvedCall<out CallableDescriptor>? {
|
||||
val context = LightClassGenerationSupport.getInstance(this.project).analyze(this)
|
||||
return this.getResolvedCall(context)
|
||||
}
|
||||
|
||||
private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) =
|
||||
(callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName
|
||||
|
||||
private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement
|
||||
|
||||
private fun psiReport(psiElement: PsiElement?): String {
|
||||
if (psiElement == null) return "null"
|
||||
val text = try {
|
||||
psiElement.text
|
||||
}
|
||||
catch (e: Exception) {
|
||||
"${e.javaClass.simpleName}:${e.message}"
|
||||
}
|
||||
return "${psiElement.javaClass.canonicalName}[$text]"
|
||||
}
|
||||
+147
-222
@@ -16,36 +16,41 @@
|
||||
|
||||
package org.jetbrains.kotlin.asJava.elements
|
||||
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
|
||||
import org.jetbrains.kotlin.asJava.classes.cannotModify
|
||||
import org.jetbrains.kotlin.asJava.classes.lazyPub
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.CompileTimeConstantUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.declaresOrInheritsDefaultValue
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.asJava.elements.lightAnnotations")
|
||||
|
||||
abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: () -> PsiAnnotation) :
|
||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||
override val clsDelegate by lazyPub(computeDelegate)
|
||||
KtLightElementBase(parent), PsiAnnotation, KtLightElement<KtCallElement, PsiAnnotation> {
|
||||
|
||||
private val _clsDelegate: PsiAnnotation by lazyPub(computeDelegate)
|
||||
|
||||
override val clsDelegate: PsiAnnotation
|
||||
get() {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode && this !is KtLightNonSourceAnnotation)
|
||||
LOG.error("KtLightAbstractAnnotation clsDelegate requested for ${this.javaClass}")
|
||||
return _clsDelegate
|
||||
}
|
||||
|
||||
override fun getNameReferenceElement() = clsDelegate.nameReferenceElement
|
||||
|
||||
@@ -64,8 +69,6 @@ abstract class KtLightAbstractAnnotation(parent: PsiElement, computeDelegate: ()
|
||||
open fun fqNameMatches(fqName: String): Boolean = qualifiedName == fqName
|
||||
}
|
||||
|
||||
private typealias AnnotationValueOrigin = () -> PsiElement?
|
||||
|
||||
class KtLightAnnotationForSourceEntry(
|
||||
private val qualifiedName: String,
|
||||
override val kotlinOrigin: KtCallElement,
|
||||
@@ -75,209 +78,80 @@ class KtLightAnnotationForSourceEntry(
|
||||
|
||||
override fun getQualifiedName() = qualifiedName
|
||||
|
||||
open inner class LightElementValue<out D : PsiElement>(
|
||||
val delegate: D,
|
||||
private val parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : PsiAnnotationMemberValue, PsiCompiledElement, PsiElement by delegate {
|
||||
override fun getMirror(): PsiElement = delegate
|
||||
|
||||
val originalExpression: PsiElement? by lazyPub(valueOrigin)
|
||||
|
||||
fun getConstantValue(): Any? {
|
||||
val expression = originalExpression as? KtExpression ?: return null
|
||||
val annotationEntry = this@KtLightAnnotationForSourceEntry.kotlinOrigin
|
||||
val context = LightClassGenerationSupport.getInstance(project).analyze(annotationEntry)
|
||||
return context[BindingContext.COMPILE_TIME_VALUE, expression]?.getValue(TypeUtils.NO_EXPECTED_TYPE)
|
||||
}
|
||||
|
||||
override fun getReference() = references.singleOrNull()
|
||||
override fun getReferences() = originalExpression?.references.orEmpty()
|
||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
||||
override fun getNavigationElement() = originalExpression
|
||||
override fun isPhysical(): Boolean = originalExpression?.containingFile == kotlinOrigin.containingFile
|
||||
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
||||
override fun getStartOffsetInParent() = originalExpression?.startOffsetInParent ?: 0
|
||||
override fun getParent() = parent
|
||||
override fun getText() = originalExpression?.text.orEmpty()
|
||||
override fun getContainingFile(): PsiFile? = if (isPhysical) kotlinOrigin.containingFile else delegate.containingFile
|
||||
|
||||
override fun replace(newElement: PsiElement): PsiElement {
|
||||
val value = (newElement as? PsiLiteral)?.value as? String ?: return this
|
||||
val origin = originalExpression
|
||||
|
||||
val exprToReplace =
|
||||
if (origin is KtCallExpression /*arrayOf*/) {
|
||||
unwrapArray(origin.valueArguments)
|
||||
}
|
||||
else {
|
||||
origin as? KtExpression
|
||||
} ?: return this
|
||||
exprToReplace.replace(KtPsiFactory(this).createExpression("\"${StringUtil.escapeStringCharacters(value)}\""))
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMemberValueAsCallArgument(memberValue: PsiElement, callHolder: KtCallElement): PsiElement? {
|
||||
val resolvedCall = callHolder.getResolvedCall() ?: return null
|
||||
val annotationConstructor = resolvedCall.resultingDescriptor
|
||||
val parameterName =
|
||||
memberValue.getNonStrictParentOfType<PsiNameValuePair>()?.name ?:
|
||||
memberValue.getNonStrictParentOfType<PsiAnnotationMethod>()?.takeIf { it.containingClass?.isAnnotationType == true }?.name ?:
|
||||
"value"
|
||||
|
||||
val parameter = annotationConstructor.valueParameters.singleOrNull { it.name.asString() == parameterName } ?: return null
|
||||
val resolvedArgument = resolvedCall.valueArguments[parameter] ?: return null
|
||||
return when (resolvedArgument) {
|
||||
is DefaultValueArgument -> {
|
||||
val psi = parameter.source.getPsi()
|
||||
when (psi) {
|
||||
is KtParameter -> psi.defaultValue
|
||||
is PsiAnnotationMethod -> psi.defaultValue
|
||||
else -> error("$psi of type ${psi?.javaClass}")
|
||||
}
|
||||
}
|
||||
|
||||
is ExpressionValueArgument -> {
|
||||
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
||||
argExpression?.asKtCall()
|
||||
?: argExpression
|
||||
?: error("resolvedArgument ($resolvedArgument) has no arg expression")
|
||||
}
|
||||
|
||||
is VarargValueArgument ->
|
||||
memberValue.unwrapArray(resolvedArgument.arguments)
|
||||
?: resolvedArgument.arguments.first().asElement().let {
|
||||
(it as? KtValueArgument)
|
||||
?.takeIf {
|
||||
it.getSpreadElement() != null ||
|
||||
it.getArgumentName() != null ||
|
||||
it.getArgumentExpression() is KtCollectionLiteralExpression
|
||||
}
|
||||
?.getArgumentExpression() ?: it.parent
|
||||
}
|
||||
|
||||
else -> error("resolvedArgument: ${resolvedArgument.javaClass} cant be processed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement.unwrapArray(arguments: List<ValueArgument>): PsiElement? {
|
||||
val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return null
|
||||
val exprIndex = arrayInitializer.initializers.indexOf(this)
|
||||
if (exprIndex < 0 || exprIndex >= arguments.size) return null
|
||||
return arguments[exprIndex].getArgumentExpression()
|
||||
}
|
||||
|
||||
open inner class LightExpressionValue<out D : PsiExpression>(
|
||||
delegate: D,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<D>(delegate, parent, valueOrigin), PsiExpression {
|
||||
override fun getType(): PsiType? = delegate.type
|
||||
}
|
||||
|
||||
inner class LightPsiLiteral(
|
||||
delegate: PsiLiteralExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiLiteralExpression>(delegate, parent, valueOrigin), PsiLiteralExpression {
|
||||
override fun getValue() = delegate.value
|
||||
}
|
||||
|
||||
inner class LightClassLiteral(
|
||||
delegate: PsiClassObjectAccessExpression,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightExpressionValue<PsiClassObjectAccessExpression>(delegate, parent, valueOrigin), PsiClassObjectAccessExpression {
|
||||
override fun getType() = delegate.type
|
||||
override fun getOperand(): PsiTypeElement = delegate.operand
|
||||
}
|
||||
|
||||
inner class LightArrayInitializerValue(
|
||||
delegate: PsiArrayInitializerMemberValue,
|
||||
parent: PsiElement,
|
||||
valueOrigin: AnnotationValueOrigin
|
||||
) : LightElementValue<PsiArrayInitializerMemberValue>(delegate, parent, valueOrigin), PsiArrayInitializerMemberValue {
|
||||
private val _initializers by lazyPub {
|
||||
delegate.initializers.mapIndexed { i, memberValue ->
|
||||
wrapAnnotationValue(memberValue, this, {
|
||||
originalExpression.let { ktOrigin ->
|
||||
when {
|
||||
ktOrigin is KtCallExpression
|
||||
&& memberValue is PsiAnnotation
|
||||
&& isAnnotationConstructorCall(ktOrigin, memberValue) -> ktOrigin
|
||||
ktOrigin is KtValueArgumentList -> ktOrigin.arguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCallElement -> ktOrigin.valueArguments.getOrNull(i)?.getArgumentExpression()
|
||||
ktOrigin is KtCollectionLiteralExpression -> ktOrigin.getInnerExpressions().getOrNull(i)
|
||||
delegate.initializers.size == 1 -> ktOrigin
|
||||
else -> null
|
||||
}.also {
|
||||
if (it == null)
|
||||
LOG.error("error wrapping ${memberValue.javaClass} for ${ktOrigin?.javaClass} in ${ktOrigin?.containingFile}",
|
||||
Attachment(
|
||||
"source_fragments.txt",
|
||||
"origin: '${psiReport(ktOrigin)}', delegate: ${psiReport(delegate)}, parent: ${psiReport(parent)}"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
override fun getInitializers() = _initializers
|
||||
}
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue, parent: PsiElement, ktOrigin: AnnotationValueOrigin): PsiAnnotationMemberValue =
|
||||
when {
|
||||
value is PsiLiteralExpression -> LightPsiLiteral(value, parent, ktOrigin)
|
||||
value is PsiClassObjectAccessExpression -> LightClassLiteral(value, parent, ktOrigin)
|
||||
value is PsiExpression -> LightExpressionValue(value, parent, ktOrigin)
|
||||
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value, parent, ktOrigin)
|
||||
value is PsiAnnotation -> {
|
||||
val origin = ktOrigin()
|
||||
val ktCallElement = origin?.asKtCall()
|
||||
val qualifiedName = value.qualifiedName
|
||||
if (qualifiedName != null && ktCallElement != null)
|
||||
KtLightAnnotationForSourceEntry(qualifiedName, ktCallElement, parent, { value })
|
||||
else {
|
||||
LOG.error("can't convert ${origin?.javaClass} to KtCallElement in ${origin?.containingFile} (value = ${value.javaClass})",
|
||||
Attachment("source_fragments.txt", "origin: '${psiReport(origin)}', value: '${psiReport(value)}'"))
|
||||
LightElementValue(value, parent, ktOrigin) // or maybe create a LightErrorAnnotationMemberValue instead?
|
||||
}
|
||||
}
|
||||
else -> LightElementValue(value, parent, ktOrigin)
|
||||
}
|
||||
|
||||
override fun isPhysical() = true
|
||||
|
||||
override fun getName() = null
|
||||
override fun getName(): String? = null
|
||||
|
||||
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue = wrapAnnotationValue(value, this, {
|
||||
getMemberValueAsCallArgument(value, kotlinOrigin)
|
||||
})
|
||||
override fun findAttributeValue(name: String?) = getAttributeValue(name, true)
|
||||
|
||||
override fun findAttributeValue(name: String?) = clsDelegate.findAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
override fun findDeclaredAttributeValue(name: String?): PsiAnnotationMemberValue? = getAttributeValue(name, false)
|
||||
|
||||
override fun findDeclaredAttributeValue(name: String?) = clsDelegate.findDeclaredAttributeValue(name)?.let { wrapAnnotationValue(it) }
|
||||
private fun getAttributeValue(name: String?, useDefault: Boolean): PsiAnnotationMemberValue? {
|
||||
val name = name ?: "value"
|
||||
|
||||
override fun getParameterList(): PsiAnnotationParameterList = KtLightAnnotationParameterList(super.getParameterList())
|
||||
val resolvedCall = kotlinOrigin.getResolvedCall() ?: return null
|
||||
val callEntry = resolvedCall.valueArguments.entries.find { (param, _) -> param.name.asString() == name } ?: return null
|
||||
|
||||
inner class KtLightAnnotationParameterList(private val list: PsiAnnotationParameterList) : KtLightElementBase(this),
|
||||
val valueArguments = callEntry.value.arguments
|
||||
val valueArgument = valueArguments.firstOrNull()
|
||||
val argument = valueArgument?.getArgumentExpression()
|
||||
if (argument != null) {
|
||||
val arrayExpected = callEntry.key?.type?.let { KotlinBuiltIns.isArray(it) } ?: false
|
||||
|
||||
if (arrayExpected && (argument is KtStringTemplateExpression || argument is KtConstantExpression || getAnnotationName(argument) != null))
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
PsiTreeUtil.findCommonParent(valueArguments.map { it.getArgumentExpression() }) as KtElement,
|
||||
this,
|
||||
{ self ->
|
||||
valueArguments.mapNotNull {
|
||||
it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) }
|
||||
}
|
||||
})
|
||||
|
||||
ktLightAnnotationParameterList.attributes.find { (it as KtLightPsiNameValuePair).valueArgument === valueArgument }?.let {
|
||||
return it.value
|
||||
}
|
||||
}
|
||||
|
||||
if (useDefault && callEntry.key.declaresOrInheritsDefaultValue()) {
|
||||
val psiElement = callEntry.key.source.getPsi()
|
||||
when (psiElement) {
|
||||
is KtParameter ->
|
||||
return psiElement.defaultValue?.let { convertToLightAnnotationMemberValue(this, it) }
|
||||
is PsiAnnotationMethod ->
|
||||
return psiElement.defaultValue
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? {
|
||||
val reference = (kotlinOrigin as? KtAnnotationEntry)?.typeReference?.reference
|
||||
?: (kotlinOrigin.calleeExpression as? KtNameReferenceExpression)?.reference
|
||||
?: return null
|
||||
return KtLightPsiJavaCodeReferenceElement(
|
||||
kotlinOrigin.navigationElement,
|
||||
reference,
|
||||
{ super.getNameReferenceElement()!! })
|
||||
}
|
||||
|
||||
|
||||
private val ktLightAnnotationParameterList by lazyPub { KtLightAnnotationParameterList() }
|
||||
|
||||
override fun getParameterList(): PsiAnnotationParameterList = ktLightAnnotationParameterList
|
||||
|
||||
inner class KtLightAnnotationParameterList() : KtLightElementBase(this),
|
||||
PsiAnnotationParameterList {
|
||||
override val kotlinOrigin get() = null
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = list.attributes.map { KtLightPsiNameValuePair(it) }.toTypedArray()
|
||||
|
||||
inner class KtLightPsiNameValuePair(private val psiNameValuePair: PsiNameValuePair) : KtLightElementBase(this),
|
||||
PsiNameValuePair {
|
||||
override fun setValue(newValue: PsiAnnotationMemberValue): PsiAnnotationMemberValue = psiNameValuePair.setValue(newValue)
|
||||
override fun getNameIdentifier(): PsiIdentifier? = psiNameValuePair.nameIdentifier
|
||||
override fun getLiteralValue(): String? = (value as? PsiLiteralValue)?.value?.toString()
|
||||
override val kotlinOrigin: KtElement? = null
|
||||
|
||||
override fun getValue(): PsiAnnotationMemberValue? = psiNameValuePair.value?.let { wrapAnnotationValue(it) }
|
||||
private val _attributes: Array<PsiNameValuePair> by lazyPub {
|
||||
this@KtLightAnnotationForSourceEntry.kotlinOrigin.valueArguments.map { KtLightPsiNameValuePair(it as KtValueArgument, this) }
|
||||
.toTypedArray<PsiNameValuePair>()
|
||||
}
|
||||
|
||||
override fun getAttributes(): Array<PsiNameValuePair> = _attributes
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -297,10 +171,10 @@ class KtLightAnnotationForSourceEntry(
|
||||
}
|
||||
|
||||
class KtLightNonSourceAnnotation(
|
||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||
parent: PsiElement, clsDelegate: PsiAnnotation
|
||||
) : KtLightAbstractAnnotation(parent, { clsDelegate }) {
|
||||
override val kotlinOrigin: KtAnnotationEntry? get() = null
|
||||
override fun getQualifiedName() = clsDelegate.qualifiedName
|
||||
override fun getQualifiedName() = kotlinOrigin?.name ?: clsDelegate.qualifiedName
|
||||
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?) = cannotModify()
|
||||
override fun findAttributeValue(attributeName: String?) = clsDelegate.findAttributeValue(attributeName)
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = clsDelegate.findDeclaredAttributeValue(attributeName)
|
||||
@@ -349,7 +223,9 @@ class KtLightNullabilityAnnotation(member: KtLightElement<*, PsiModifierListOwne
|
||||
|
||||
override fun findAttributeValue(attributeName: String?) = null
|
||||
|
||||
override fun getQualifiedName(): String? = clsDelegate.qualifiedName
|
||||
override fun getQualifiedName(): String? = Nullable::class.java.name
|
||||
|
||||
override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? = null
|
||||
|
||||
override fun findDeclaredAttributeValue(attributeName: String?) = null
|
||||
}
|
||||
@@ -363,18 +239,67 @@ private fun KtElement.getResolvedCall(): ResolvedCall<out CallableDescriptor>? {
|
||||
return this.getResolvedCall(context)
|
||||
}
|
||||
|
||||
private fun isAnnotationConstructorCall(callExpression: KtCallExpression, psiAnnotation: PsiAnnotation) =
|
||||
(callExpression.getResolvedCall()?.resultingDescriptor as? ClassConstructorDescriptor)?.constructedClass?.fqNameUnsafe?.toString() == psiAnnotation.qualifiedName
|
||||
|
||||
private fun PsiElement.asKtCall(): KtCallElement? = (this as? KtElement)?.getResolvedCall()?.call?.callElement as? KtCallElement
|
||||
|
||||
private fun psiReport(psiElement: PsiElement?): String {
|
||||
if (psiElement == null) return "null"
|
||||
val text = try {
|
||||
psiElement.text
|
||||
fun convertToLightAnnotationMemberValue(lightParent: PsiElement, argument: KtExpression): PsiAnnotationMemberValue {
|
||||
val argument = unwrapCall(argument)
|
||||
when (argument) {
|
||||
is KtStringTemplateExpression, is KtConstantExpression -> {
|
||||
return KtLightPsiLiteral(argument, lightParent)
|
||||
}
|
||||
is KtCallExpression -> {
|
||||
val arguments = argument.valueArguments
|
||||
val annotationName = argument.calleeExpression?.let { getAnnotationName(it) }
|
||||
if (annotationName != null) {
|
||||
return KtLightAnnotationForSourceEntry(
|
||||
annotationName,
|
||||
argument,
|
||||
lightParent,
|
||||
{ throw UnsupportedOperationException("cls delegate is not supported for nested annotations") })
|
||||
}
|
||||
val resolvedCall = argument.getResolvedCall()
|
||||
if (resolvedCall != null && CompileTimeConstantUtils.isArrayFunctionCall(resolvedCall))
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
argument,
|
||||
lightParent,
|
||||
{ self ->
|
||||
arguments.mapNotNull {
|
||||
it.getArgumentExpression()?.let { convertToLightAnnotationMemberValue(self, it) }
|
||||
}
|
||||
})
|
||||
}
|
||||
is KtCollectionLiteralExpression -> {
|
||||
val arguments = argument.getInnerExpressions()
|
||||
if (arguments.isNotEmpty())
|
||||
return KtLightPsiArrayInitializerMemberValue(
|
||||
argument,
|
||||
lightParent,
|
||||
{ self -> arguments.mapNotNull { convertToLightAnnotationMemberValue(self, it) } })
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
"${e.javaClass.simpleName}:${e.message}"
|
||||
// everything else (like complex constant references) considered as PsiLiteral-s
|
||||
return KtLightPsiLiteral(argument, lightParent)
|
||||
}
|
||||
|
||||
private val KtExpression.nameReference: KtNameReferenceExpression?
|
||||
get() = when {
|
||||
this is KtConstructorCalleeExpression -> constructorReferenceExpression as? KtNameReferenceExpression
|
||||
else -> this as? KtNameReferenceExpression
|
||||
}
|
||||
return "${psiElement.javaClass.canonicalName}[$text]"
|
||||
|
||||
private fun unwrapCall(callee: KtExpression): KtExpression = when (callee) {
|
||||
is KtDotQualifiedExpression -> callee.lastChild as? KtCallExpression ?: callee
|
||||
else -> callee
|
||||
}
|
||||
|
||||
private fun getAnnotationName(callee: KtExpression): String? {
|
||||
val callee = unwrapCall(callee)
|
||||
val resultingDescriptor = callee.getResolvedCall()?.resultingDescriptor
|
||||
if (resultingDescriptor is ClassConstructorDescriptor) {
|
||||
val ktClass = resultingDescriptor.constructedClass.source.getPsi() as? KtClass
|
||||
if (ktClass?.isAnnotation() == true) return ktClass.fqName?.toString()
|
||||
}
|
||||
if (resultingDescriptor is JavaClassConstructorDescriptor) {
|
||||
val psiClass = resultingDescriptor.constructedClass.source.getPsi() as? PsiClass
|
||||
if (psiClass?.isAnnotationType == true) return psiClass.qualifiedName
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -20,10 +20,7 @@ import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightElement
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||
import org.jetbrains.kotlin.asJava.elements.*
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.java.propertyNameByGetMethodName
|
||||
import org.jetbrains.kotlin.load.java.propertyNameBySetMethodName
|
||||
@@ -139,7 +136,7 @@ val PsiElement.unwrapped: PsiElement?
|
||||
get() = when {
|
||||
this is KtLightElement<*, *> -> kotlinOrigin
|
||||
this is KtLightIdentifier -> origin
|
||||
this is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> originalExpression
|
||||
this is KtLightElementBase -> kotlinOrigin
|
||||
else -> this
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea
|
||||
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiConstantEvaluationHelper
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.intellij.psi.impl.ConstantExpressionEvaluator
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightElementBase
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
@@ -36,8 +34,7 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
|
||||
return if (constantValue is ArrayValue) {
|
||||
val items = constantValue.value.map { evalConstantValue(it) }
|
||||
items.singleOrNull() ?: items
|
||||
}
|
||||
else constantValue.value
|
||||
} else constantValue.value
|
||||
}
|
||||
|
||||
override fun computeConstantExpression(expression: PsiElement, throwExceptionOnOverflow: Boolean): Any? {
|
||||
@@ -45,32 +42,23 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
|
||||
}
|
||||
|
||||
override fun computeExpression(
|
||||
expression: PsiElement,
|
||||
throwExceptionOnOverflow: Boolean,
|
||||
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
|
||||
expression: PsiElement,
|
||||
throwExceptionOnOverflow: Boolean,
|
||||
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
|
||||
): Any? {
|
||||
if (expression !is KtLightAnnotationForSourceEntry.LightExpressionValue<*>) return null
|
||||
val expressionToCompute = expression.originalExpression ?: return null
|
||||
return when (expressionToCompute) {
|
||||
is KtExpression -> {
|
||||
val resolutionFacade = expressionToCompute.getResolutionFacade()
|
||||
val evaluator = FrontendConstantExpressionEvaluator(
|
||||
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings
|
||||
)
|
||||
val evaluatorTrace = DelegatingBindingTrace(resolutionFacade.analyze(expressionToCompute), "Evaluating annotation argument")
|
||||
|
||||
val constant = evaluator.evaluateExpression(expressionToCompute, evaluatorTrace) ?: return null
|
||||
if (constant.isError) return null
|
||||
evalConstantValue(constant.toConstantValue(TypeUtils.NO_EXPECTED_TYPE))
|
||||
}
|
||||
|
||||
is PsiExpression -> {
|
||||
JavaPsiFacade.getInstance(expressionToCompute.project)
|
||||
.constantEvaluationHelper
|
||||
.computeExpression(expressionToCompute, throwExceptionOnOverflow, auxEvaluator)
|
||||
}
|
||||
|
||||
else -> null
|
||||
val expressionToCompute = when (expression) {
|
||||
is KtLightElementBase -> expression.kotlinOrigin as? KtExpression ?: return null
|
||||
else -> return null
|
||||
}
|
||||
|
||||
val resolutionFacade = expressionToCompute.getResolutionFacade()
|
||||
val evaluator = FrontendConstantExpressionEvaluator(
|
||||
resolutionFacade.moduleDescriptor, expressionToCompute.languageVersionSettings
|
||||
)
|
||||
val evaluatorTrace = DelegatingBindingTrace(resolutionFacade.analyze(expressionToCompute), "Evaluating annotation argument")
|
||||
|
||||
val constant = evaluator.evaluateExpression(expressionToCompute, evaluatorTrace) ?: return null
|
||||
if (constant.isError) return null
|
||||
return evalConstantValue(constant.toConstantValue(TypeUtils.NO_EXPECTED_TYPE))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
package org.jetbrains.kotlin.asJava
|
||||
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
|
||||
@@ -78,8 +82,9 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single()
|
||||
.expectAnnotations(2).single { it.qualifiedName == "Qualifier" }
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiExpression
|
||||
assertTextRangeAndValue("\"foo\"", "foo", annotationAttributeVal)
|
||||
TestCase.assertEquals(PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)), annotationAttributeVal.type)
|
||||
}
|
||||
|
||||
fun testAnnotationsInAnnotationsDeclarations() {
|
||||
@@ -102,6 +107,107 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
|
||||
}
|
||||
|
||||
fun testConstants() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public class Constants {
|
||||
|
||||
public static final String MY_CONSTANT = "67";
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation(Constants.MY_CONSTANT)
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("Constants.MY_CONSTANT", annotationAttributeVal)
|
||||
TestCase.assertEquals("67", annotationAttributeVal.value)
|
||||
TestCase.assertEquals(
|
||||
PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)),
|
||||
(annotationAttributeVal as PsiExpression).type
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun testLiteralReplace() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation("oldValue")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("\"oldValue\"", annotationAttributeVal)
|
||||
TestCase.assertEquals("oldValue", annotationAttributeVal.value)
|
||||
runWriteAction {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
annotationAttributeVal.replace(
|
||||
JavaPsiFacade.getElementFactory(project).createExpressionFromText("\"newValue\"", annotationAttributeVal)
|
||||
)
|
||||
}
|
||||
}
|
||||
myFixture.checkResult(
|
||||
"""
|
||||
@StringAnnotation("newValue")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testReferences() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation("someText")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("\"someText\"", annotationAttributeVal)
|
||||
TestCase.assertTrue(
|
||||
"String literal references should be available via light literal",
|
||||
annotationAttributeVal.references.any {
|
||||
/* FileReferences are injected in every string, so we use them as indicator that KtElement references are available there */
|
||||
it is FileReference
|
||||
})
|
||||
}
|
||||
|
||||
fun testAnnotationsInAnnotationsArrayDeclarations() {
|
||||
myFixture.addClass("""
|
||||
public @interface OuterAnnotation {
|
||||
@@ -241,6 +347,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
val annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single()
|
||||
|
||||
val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.parent.assertInstanceOf<PsiNameValuePair>().let { pair ->
|
||||
TestCase.assertEquals("anno2", pair.name)
|
||||
}
|
||||
|
||||
assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
|
||||
@@ -248,6 +358,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
innerAnnotationAttributeVal as PsiAnnotation
|
||||
val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf<PsiLiteral>()
|
||||
assertTextAndRange("1", value)
|
||||
TestCase.assertEquals(PsiType.INT, value.assertInstanceOf<PsiExpression>().type)
|
||||
}
|
||||
|
||||
|
||||
@@ -278,7 +389,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
|
||||
annotations[0].let { annotation ->
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
assertTextAndRange("(Inner())", annotationAttributeVal)
|
||||
assertTextAndRange("Inner()", annotationAttributeVal)
|
||||
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("Inner()", innerAnnotationAttributeVal)
|
||||
@@ -423,7 +534,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
}
|
||||
annotations[1].let { annotation ->
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
assertTextAndRange("(\"1\")", annotationAttributeVal)
|
||||
assertTextAndRange("\"1\"", annotationAttributeVal)
|
||||
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
|
||||
@@ -483,7 +594,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotation = annotations.first()
|
||||
assertTextAndRange("", annotation.findAttributeValue("i")!!)
|
||||
assertTextAndRange("true", annotation.findAttributeValue("i")!!)
|
||||
}
|
||||
|
||||
fun testMissingDefault() {
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
package org.jetbrains.kotlin.asJava
|
||||
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
|
||||
@@ -78,9 +82,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
val annotation = myFixture.findClass("AnnotatedClass").methods.first { it.name == "bar" }.parameterList.parameters.single()
|
||||
.expectAnnotations(2).single { it.qualifiedName == "Qualifier" }
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiExpression
|
||||
TestCase.assertTrue(annotationAttributeVal.isPhysical)
|
||||
assertTextRangeAndValue("\"foo\"", "foo", annotationAttributeVal)
|
||||
TestCase.assertEquals(PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)), annotationAttributeVal.type)
|
||||
}
|
||||
|
||||
fun testAnnotationsInAnnotationsDeclarations() {
|
||||
@@ -103,6 +108,107 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
assertTextAndRange("InnerAnnotation()", annotationAttributeVal)
|
||||
}
|
||||
|
||||
fun testConstants() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public class Constants {
|
||||
|
||||
public static final String MY_CONSTANT = "67";
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation(Constants.MY_CONSTANT)
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("Constants.MY_CONSTANT", annotationAttributeVal)
|
||||
TestCase.assertEquals("67", annotationAttributeVal.value)
|
||||
TestCase.assertEquals(
|
||||
PsiType.getJavaLangString(psiManager, GlobalSearchScope.projectScope(project)),
|
||||
(annotationAttributeVal as PsiExpression).type
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun testLiteralReplace() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation("oldValue")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("\"oldValue\"", annotationAttributeVal)
|
||||
TestCase.assertEquals("oldValue", annotationAttributeVal.value)
|
||||
runWriteAction {
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
annotationAttributeVal.replace(
|
||||
JavaPsiFacade.getElementFactory(project).createExpressionFromText("\"newValue\"", annotationAttributeVal)
|
||||
)
|
||||
}
|
||||
}
|
||||
myFixture.checkResult(
|
||||
"""
|
||||
@StringAnnotation("newValue")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testReferences() {
|
||||
myFixture.addClass(
|
||||
"""
|
||||
public @interface StringAnnotation {
|
||||
String value();
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"AnnotatedClass.kt", """
|
||||
@StringAnnotation("someText")
|
||||
open class AnnotatedClass
|
||||
""".trimIndent()
|
||||
)
|
||||
myFixture.testHighlighting("AnnotatedClass.kt")
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotationAttributeVal = annotations.first().findAttributeValue("value") as PsiLiteral
|
||||
assertTextAndRange("\"someText\"", annotationAttributeVal)
|
||||
TestCase.assertTrue(
|
||||
"String literal references should be available via light literal",
|
||||
annotationAttributeVal.references.any {
|
||||
/* FileReferences are injected in every string, so we use them as indicator that KtElement references are available there */
|
||||
it is FileReference
|
||||
})
|
||||
}
|
||||
|
||||
fun testAnnotationsInAnnotationsArrayDeclarations() {
|
||||
myFixture.addClass("""
|
||||
public @interface OuterAnnotation {
|
||||
@@ -242,6 +348,10 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
val annotation = myFixture.findClass("AnnotatedClass").expectAnnotations(1).single()
|
||||
|
||||
val annotationAttributeVal = annotation.findAttributeValue("anno2") as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.parent.assertInstanceOf<PsiNameValuePair>().let { pair ->
|
||||
TestCase.assertEquals("anno2", pair.name)
|
||||
}
|
||||
|
||||
assertTextAndRange("arrayOf(Anno2(1), Anno2(2))", annotationAttributeVal)
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("Anno2(1)", innerAnnotationAttributeVal)
|
||||
@@ -249,6 +359,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
innerAnnotationAttributeVal as PsiAnnotation
|
||||
val value = innerAnnotationAttributeVal.findAttributeValue("v").assertInstanceOf<PsiLiteral>()
|
||||
assertTextAndRange("1", value)
|
||||
TestCase.assertEquals(PsiType.INT, value.assertInstanceOf<PsiExpression>().type)
|
||||
}
|
||||
|
||||
|
||||
@@ -279,7 +390,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
val annotations = myFixture.findClass("MyAnnotated").expectAnnotations(1)
|
||||
annotations[0].let { annotation ->
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
assertTextAndRange("(Inner())", annotationAttributeVal)
|
||||
assertTextAndRange("Inner()", annotationAttributeVal)
|
||||
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("Inner()", innerAnnotationAttributeVal)
|
||||
@@ -424,7 +535,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
}
|
||||
annotations[1].let { annotation ->
|
||||
val annotationAttributeVal = annotation.findAttributeValue("value") as PsiElement
|
||||
assertTextAndRange("(\"1\")", annotationAttributeVal)
|
||||
assertTextAndRange("\"1\"", annotationAttributeVal)
|
||||
annotationAttributeVal as PsiArrayInitializerMemberValue
|
||||
annotationAttributeVal.initializers[0].let { innerAnnotationAttributeVal ->
|
||||
assertTextAndRange("\"1\"", innerAnnotationAttributeVal)
|
||||
@@ -484,7 +595,7 @@ class KtLightAnnotationTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
val annotations = myFixture.findClass("AnnotatedClass").expectAnnotations(1)
|
||||
val annotation = annotations.first()
|
||||
assertTextAndRange("", annotation.findAttributeValue("i")!!)
|
||||
assertTextAndRange("true", annotation.findAttributeValue("i")!!)
|
||||
}
|
||||
|
||||
fun testMissingDefault() {
|
||||
|
||||
@@ -334,11 +334,11 @@ internal object KotlinConverter {
|
||||
|
||||
is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType)
|
||||
is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) }
|
||||
is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> {
|
||||
val expression = element.originalExpression
|
||||
is KtLightElementBase -> {
|
||||
val expression = element.kotlinOrigin
|
||||
when (expression) {
|
||||
is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType)
|
||||
else -> el<UExpression> { UastEmptyExpression }
|
||||
else -> el<UExpression> { UastEmptyExpression(givenParent) }
|
||||
}
|
||||
}
|
||||
is KtLiteralStringTemplateEntry, is KtEscapeStringTemplateEntry -> el<ULiteralExpression>(build(::KotlinStringULiteralExpression))
|
||||
|
||||
@@ -314,8 +314,8 @@ internal object KotlinConverter {
|
||||
|
||||
is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType)
|
||||
is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) }
|
||||
is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> {
|
||||
val expression = element.originalExpression
|
||||
is KtLightElementBase -> {
|
||||
val expression = element.kotlinOrigin
|
||||
when (expression) {
|
||||
is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType)
|
||||
else -> el<UExpression> { UastEmptyExpression }
|
||||
|
||||
@@ -329,8 +329,8 @@ internal object KotlinConverter {
|
||||
|
||||
is KtExpression -> KotlinConverter.convertExpression(element, givenParent, requiredType)
|
||||
is KtLambdaArgument -> element.getLambdaExpression()?.let { KotlinConverter.convertExpression(it, givenParent, requiredType) }
|
||||
is KtLightAnnotationForSourceEntry.LightExpressionValue<*> -> {
|
||||
val expression = element.originalExpression
|
||||
is KtLightElementBase -> {
|
||||
val expression = element.kotlinOrigin
|
||||
when (expression) {
|
||||
is KtExpression -> KotlinConverter.convertExpression(expression, givenParent, requiredType)
|
||||
else -> el<UExpression> { UastEmptyExpression }
|
||||
|
||||
Reference in New Issue
Block a user