201: Uast: KotlinUastCodeGenerationPlugin on the level needed for ReactorSupport (KT-38062)

This commit is contained in:
Nicolay Mitropolsky
2020-05-14 19:46:25 +03:00
parent 8434806f56
commit 0bb3a4ccb7
5 changed files with 1273 additions and 33 deletions
@@ -6,15 +6,25 @@
package org.jetbrains.uast.kotlin.generate package org.jetbrains.uast.kotlin.generate
import com.intellij.lang.Language import com.intellij.lang.Language
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiType import com.intellij.psi.PsiType
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.refactoring.fqName.fqName
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.idea.util.resolveToKotlinType
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getPossiblyQualifiedCallExpression import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.uast.* import org.jetbrains.uast.*
@@ -22,6 +32,8 @@ import org.jetbrains.uast.generate.UParameterInfo
import org.jetbrains.uast.generate.UastCodeGenerationPlugin import org.jetbrains.uast.generate.UastCodeGenerationPlugin
import org.jetbrains.uast.generate.UastElementFactory import org.jetbrains.uast.generate.UastElementFactory
import org.jetbrains.uast.kotlin.* import org.jetbrains.uast.kotlin.*
import org.jetbrains.uast.kotlin.internal.KotlinFakeUElement
import org.jetbrains.uast.kotlin.internal.toSourcePsiFakeAware
class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin { class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin {
override val language: Language override val language: Language
@@ -31,7 +43,7 @@ class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin {
KotlinUastElementFactory(project) KotlinUastElementFactory(project)
override fun <T : UElement> replace(oldElement: UElement, newElement: T, elementType: Class<T>): T? { override fun <T : UElement> replace(oldElement: UElement, newElement: T, elementType: Class<T>): T? {
val oldPsi = oldElement.sourcePsi ?: return null val oldPsi = oldElement.toSourcePsiFakeAware().singleOrNull() ?: return null
val newPsi = newElement.sourcePsi?.let { val newPsi = newElement.sourcePsi?.let {
when { when {
it is KtCallExpression && it.parent is KtQualifiedExpression -> it.parent it is KtCallExpression && it.parent is KtQualifiedExpression -> it.parent
@@ -42,9 +54,23 @@ class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin {
val psiFactory = KtPsiFactory(oldPsi.project) val psiFactory = KtPsiFactory(oldPsi.project)
val oldParentPsi = oldPsi.parent val oldParentPsi = oldPsi.parent
val (updOldPsi, updNewPsi) = when { val (updOldPsi, updNewPsi) = when {
oldParentPsi is KtStringTemplateExpression && oldParentPsi.entries.size == 1 -> oldParentPsi to newPsi oldParentPsi is KtStringTemplateExpression && oldParentPsi.entries.size == 1 ->
oldPsi is KtStringTemplateEntry && newPsi !is KtStringTemplateEntry && newPsi is KtExpression -> oldPsi to psiFactory.createBlockStringTemplateEntry(newPsi) oldParentPsi to newPsi
else -> oldPsi to newPsi
oldPsi is KtStringTemplateEntry && newPsi !is KtStringTemplateEntry && newPsi is KtExpression ->
oldPsi to psiFactory.createBlockStringTemplateEntry(newPsi)
oldPsi is KtBlockExpression && newPsi is KtBlockExpression -> {
if (!hasBraces(oldPsi) && hasBraces(newPsi)) {
oldPsi to psiFactory.createLambdaExpression("none", newPsi.statements.joinToString("\n") { "println()" }).bodyExpression!!.also {
it.statements.zip(newPsi.statements).forEach { it.first.replace(it.second) }
}
} else
oldPsi to newPsi
}
else ->
oldPsi to newPsi
} }
return when (val replaced = updOldPsi.replace(updNewPsi)?.safeAs<KtElement>()?.let { ShortenReferences.DEFAULT.process(it) }) { return when (val replaced = updOldPsi.replace(updNewPsi)?.safeAs<KtElement>()?.let { ShortenReferences.DEFAULT.process(it) }) {
@@ -54,10 +80,18 @@ class KotlinUastCodeGenerationPlugin : UastCodeGenerationPlugin {
} }
} }
private fun hasBraces(oldPsi: KtBlockExpression): Boolean = oldPsi.lBrace != null && oldPsi.rBrace != null
class KotlinUastElementFactory(project: Project) : UastElementFactory { class KotlinUastElementFactory(project: Project) : UastElementFactory {
private val psiFactory = KtPsiFactory(project) private val psiFactory = KtPsiFactory(project)
@Deprecated("use version with context parameter")
override fun createQualifiedReference(qualifiedName: String, context: UElement?): UQualifiedReferenceExpression? { override fun createQualifiedReference(qualifiedName: String, context: UElement?): UQualifiedReferenceExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createQualifiedReference(qualifiedName, context?.sourcePsi)
}
/*override*/ fun createQualifiedReference(qualifiedName: String, context: PsiElement?): UQualifiedReferenceExpression? {
return psiFactory.createExpression(qualifiedName).let { return psiFactory.createExpression(qualifiedName).let {
when (it) { when (it) {
is KtDotQualifiedExpression -> KotlinUQualifiedReferenceExpression(it, null) is KtDotQualifiedExpression -> KotlinUQualifiedReferenceExpression(it, null)
@@ -68,21 +102,43 @@ class KotlinUastElementFactory(project: Project) : UastElementFactory {
} }
} }
override fun createCallExpression(receiver: UExpression?, methodName: String, parameters: List<UExpression>, expectedReturnType: PsiType?, kind: UastCallKind, context: PsiElement?): UCallExpression? { override fun createCallExpression(
receiver: UExpression?,
methodName: String,
parameters: List<UExpression>,
expectedReturnType: PsiType?,
kind: UastCallKind,
context: PsiElement?
): UCallExpression? {
if (kind != UastCallKind.METHOD_CALL) return null if (kind != UastCallKind.METHOD_CALL) return null
val typeParams = (context as? KtElement)?.let { kontext ->
val resolutionFacade = kontext.getResolutionFacade()
(expectedReturnType as? PsiClassType)?.parameters?.map { it.resolveToKotlinType(resolutionFacade) }
}
val name = methodName.quoteIfNeeded() val name = methodName.quoteIfNeeded()
val methodCall = psiFactory.createExpression( val methodCall = psiFactory.createExpression(
if (receiver != null) "a.$name()" else "$name()" buildString {
if (receiver != null)
append("a.")
append(name)
if (typeParams != null) {
append(typeParams.joinToString(", ", "<", ">") { type ->
type.fqName?.asString() ?: ""
})
}
append("()")
}
).getPossiblyQualifiedCallExpression() ?: return null ).getPossiblyQualifiedCallExpression() ?: return null
if (receiver != null) { if (receiver != null) {
methodCall.parent.safeAs<KtDotQualifiedExpression>()?.receiverExpression?.replace(receiver.sourcePsi!!) methodCall.parent.safeAs<KtDotQualifiedExpression>()?.receiverExpression?.replace(wrapULiteral(receiver).sourcePsi!!)
} }
val valueArgumentList = methodCall.valueArgumentList val valueArgumentList = methodCall.valueArgumentList
for (parameter in parameters) { for (parameter in parameters) {
valueArgumentList?.addArgument(psiFactory.createArgument(parameter.sourcePsi as? KtExpression)) valueArgumentList?.addArgument(psiFactory.createArgument(wrapULiteral(parameter).sourcePsi as KtExpression))
} }
return KotlinUFunctionCallExpression(methodCall, null) return KotlinUFunctionCallExpression(methodCall, null)
@@ -93,65 +149,281 @@ class KotlinUastElementFactory(project: Project) : UastElementFactory {
return KotlinStringULiteralExpression(psiFactory.createExpression(StringUtil.wrapWithDoubleQuote(text)), null) return KotlinStringULiteralExpression(psiFactory.createExpression(StringUtil.wrapWithDoubleQuote(text)), null)
} }
/*override*/ fun createNullLiteral(context: PsiElement?): ULiteralExpression {
return psiFactory.createExpression("null").toUElementOfType<ULiteralExpression>()!!
}
/*override*/ fun createIntLiteral(value: Int, context: PsiElement?): ULiteralExpression {
return psiFactory.createExpression(value.toString()).toUElementOfType<ULiteralExpression>()!!
}
private fun KtExpression.ensureBlockExpressionBraces(): KtExpression {
if (this !is KtBlockExpression || hasBraces(this)) return this
val blockExpression = psiFactory.createBlock(this.statements.joinToString("\n") { "println()" })
for ((placeholder, statement) in blockExpression.statements.zip(this.statements)) {
placeholder.replace(statement)
}
return blockExpression
}
@Deprecated("use version with context parameter")
override fun createIfExpression(condition: UExpression, thenBranch: UExpression, elseBranch: UExpression?): UIfExpression? { override fun createIfExpression(condition: UExpression, thenBranch: UExpression, elseBranch: UExpression?): UIfExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createIfExpression(condition, thenBranch, elseBranch, null)
}
/*override*/ fun createIfExpression(
condition: UExpression,
thenBranch: UExpression,
elseBranch: UExpression?,
context: PsiElement?
): UIfExpression? {
val conditionPsi = condition.sourcePsi as? KtExpression ?: return null val conditionPsi = condition.sourcePsi as? KtExpression ?: return null
val thenBranchPsi = thenBranch.sourcePsi as? KtExpression ?: return null val thenBranchPsi = thenBranch.sourcePsi as? KtExpression ?: return null
val elseBranchPsi = elseBranch?.sourcePsi as? KtExpression val elseBranchPsi = elseBranch?.sourcePsi as? KtExpression
return KotlinUIfExpression(psiFactory.createIf(conditionPsi, thenBranchPsi, elseBranchPsi), null) return KotlinUIfExpression(psiFactory.createIf(conditionPsi, thenBranchPsi.ensureBlockExpressionBraces(), elseBranchPsi?.ensureBlockExpressionBraces()), null)
} }
@Deprecated("use version with context parameter")
override fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? { override fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createParenthesizedExpression(expression, null)
}
/*override*/ fun createParenthesizedExpression(expression: UExpression, context: PsiElement?): UParenthesizedExpression? {
val source = expression.sourcePsi ?: return null val source = expression.sourcePsi ?: return null
val parenthesized = psiFactory.createExpression("(${source.text})") as? KtParenthesizedExpression ?: return null val parenthesized = psiFactory.createExpression("(${source.text})") as? KtParenthesizedExpression ?: return null
return KotlinUParenthesizedExpression(parenthesized, null) return KotlinUParenthesizedExpression(parenthesized, null)
} }
@Deprecated("use version with context parameter")
override fun createSimpleReference(name: String): USimpleNameReferenceExpression? { override fun createSimpleReference(name: String): USimpleNameReferenceExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createSimpleReference(name, null)
}
/*override*/ fun createSimpleReference(name: String, context: PsiElement?): USimpleNameReferenceExpression? {
return KotlinUSimpleReferenceExpression(psiFactory.createSimpleName(name), null) return KotlinUSimpleReferenceExpression(psiFactory.createSimpleName(name), null)
} }
@Deprecated("use version with context parameter")
override fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression? { override fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression? {
return createSimpleReference(variable.name ?: return null) logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createSimpleReference(variable, null)
} }
/*override*/ fun createSimpleReference(variable: UVariable, context: PsiElement?): USimpleNameReferenceExpression? {
return createSimpleReference(variable.name ?: return null, context)
}
@Deprecated("use version with context parameter")
override fun createReturnExpresion(expression: UExpression?, inLambda: Boolean): UReturnExpression? { override fun createReturnExpresion(expression: UExpression?, inLambda: Boolean): UReturnExpression? {
val returnExpression = psiFactory.createExpression("return") as KtReturnExpression logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
expression?.sourcePsi?.let { returnExpression.add(it) } return createReturnExpresion(expression, inLambda, null)
}
/*override*/ fun createReturnExpresion(expression: UExpression?, inLambda: Boolean, context: PsiElement?): UReturnExpression? {
val label = if (inLambda && context != null) getParentLambdaLabelName(context)?.let { "@$it" } ?: "" else ""
val returnExpression = psiFactory.createExpression("return$label 1") as KtReturnExpression
val sourcePsi = expression?.sourcePsi
if (sourcePsi != null) {
returnExpression.returnedExpression!!.replace(sourcePsi)
} else {
returnExpression.returnedExpression?.delete()
}
return KotlinUReturnExpression(returnExpression, null) return KotlinUReturnExpression(returnExpression, null)
} }
override fun createBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UBinaryExpression? { private fun getParentLambdaLabelName(context: PsiElement): String? {
val lambdaExpression = context.getParentOfType<KtLambdaExpression>(false) ?: return null
lambdaExpression.parent.safeAs<KtLabeledExpression>()?.let { return it.getLabelName() }
val callExpression = lambdaExpression.getStrictParentOfType<KtCallExpression>() ?: return null
callExpression.valueArguments.find {
it.getArgumentExpression()?.unpackFunctionLiteral(allowParentheses = false) === lambdaExpression
} ?: return null
return callExpression.getCallNameExpression()?.text
}
@Deprecated("use version with context parameter")
override fun createBinaryExpression(
leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator
): UBinaryExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createBinaryExpression(leftOperand, rightOperand, operator, null)
}
/*override*/ fun createBinaryExpression(
leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator,
context: PsiElement?
): UBinaryExpression? {
val binaryExpression = joinBinaryExpression(leftOperand, rightOperand, operator) ?: return null
return KotlinUBinaryExpression(binaryExpression, null)
}
private fun joinBinaryExpression(
leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator
): KtBinaryExpression? {
val leftPsi = leftOperand.sourcePsi ?: return null val leftPsi = leftOperand.sourcePsi ?: return null
val rightPsi = rightOperand.sourcePsi ?: return null val rightPsi = rightOperand.sourcePsi ?: return null
val binaryExpression = psiFactory.createExpression("a ${operator.text} b") as? KtBinaryExpression ?: return null val binaryExpression = psiFactory.createExpression("a ${operator.text} b") as? KtBinaryExpression ?: return null
binaryExpression.left?.replace(leftPsi) binaryExpression.left?.replace(leftPsi)
binaryExpression.right?.replace(rightPsi) binaryExpression.right?.replace(rightPsi)
return KotlinUBinaryExpression(binaryExpression, null) return binaryExpression
} }
override fun createFlatBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UPolyadicExpression? { @Deprecated("use version with context parameter")
return createBinaryExpression(leftOperand, rightOperand, operator) override fun createFlatBinaryExpression(
leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator
): UPolyadicExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createFlatBinaryExpression(leftOperand, rightOperand, operator, null)
} }
/*override*/ fun createFlatBinaryExpression(
leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator,
context: PsiElement?
): UPolyadicExpression? {
fun unwrapParentheses(exp: KtExpression?) {
if (exp !is KtParenthesizedExpression) return
if (!KtPsiUtil.areParenthesesUseless(exp)) return
exp.expression?.let { exp.replace(it) }
}
val binaryExpression = joinBinaryExpression(leftOperand, rightOperand, operator) ?: return null
unwrapParentheses(binaryExpression.left)
unwrapParentheses(binaryExpression.right)
return psiFactory.createExpression(binaryExpression.text).toUElementOfType()!!
}
@Deprecated("use version with context parameter")
override fun createBlockExpression(expressions: List<UExpression>): UBlockExpression? { override fun createBlockExpression(expressions: List<UExpression>): UBlockExpression? {
if (expressions.any { it.sourcePsi == null}) return null logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
val block = psiFactory.createBlock(expressions.joinToString(separator = "\n") { it.sourcePsi?.text ?: "" }) return createBlockExpression(expressions, null)
}
/*override*/ fun createBlockExpression(expressions: List<UExpression>, context: PsiElement?): UBlockExpression? {
val sourceExpressions = expressions.flatMap { it.toSourcePsiFakeAware() }
val block = psiFactory.createBlock(
sourceExpressions.joinToString(separator = "\n") { "println()" }
)
for ((placeholder, psiElement) in block.statements.zip(sourceExpressions)) {
placeholder.replace(psiElement)
}
return KotlinUBlockExpression(block, null) return KotlinUBlockExpression(block, null)
} }
@Deprecated("use version with context parameter")
override fun createDeclarationExpression(declarations: List<UDeclaration>): UDeclarationsExpression? { override fun createDeclarationExpression(declarations: List<UDeclaration>): UDeclarationsExpression? {
return object : KotlinUDeclarationsExpression(null) { logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createDeclarationExpression(declarations, null)
}
/*override*/ fun createDeclarationExpression(declarations: List<UDeclaration>, context: PsiElement?): UDeclarationsExpression? {
return object : KotlinUDeclarationsExpression(null), KotlinFakeUElement {
override var declarations: List<UDeclaration> = declarations override var declarations: List<UDeclaration> = declarations
override fun unwrapToSourcePsi(): List<PsiElement> = declarations.flatMap { it.toSourcePsiFakeAware() }
} }
} }
override fun createLambdaExpression(parameters: List<UParameterInfo>, body: UExpression): ULambdaExpression? { /*override*/ fun createLambdaExpression(
TODO("Not yet implemented") parameters: List<UParameterInfo>,
body: UExpression,
context: PsiElement?
): ULambdaExpression? {
val resolutionFacade = (context as? KtElement)?.getResolutionFacade()
val validator = (context as? KtElement)?.let { usedNamesFilter(it) } ?: { true }
val newLambdaStatements = if (body is UBlockExpression) {
body.expressions.flatMap { member ->
when {
member is UReturnExpression -> member.returnExpression?.toSourcePsiFakeAware().orEmpty()
else -> member.toSourcePsiFakeAware()
}
}
} else
listOf(body.sourcePsi!!)
val ktLambdaExpression = psiFactory.createLambdaExpression(
parameters.joinToString(", ") { p ->
val ktype = resolutionFacade?.let { p.type?.resolveToKotlinType(it) }
StringBuilder().apply {
append(p.suggestedName ?: ktype?.let { KotlinNameSuggester.suggestNamesByType(it, validator).firstOrNull() })
?: KotlinNameSuggester.suggestNameByName("v", validator)
ktype?.fqName?.toString()?.let { append(": ").append(it) }
}
},
newLambdaStatements.joinToString("\n") { "placeholder" }
)
for ((old, new) in ktLambdaExpression.bodyExpression!!.statements.zip(newLambdaStatements)) {
old.replace(new)
}
return ktLambdaExpression.toUElementOfType()!!
} }
override fun createLocalVariable(suggestedName: String?, type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? { @Deprecated("use version with context parameter")
TODO("Not yet implemented") override fun createLambdaExpression(parameters: List<UParameterInfo>, body: UExpression): ULambdaExpression? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createLambdaExpression(parameters, body, null)
} }
/*override*/ fun createLocalVariable(
suggestedName: String?,
type: PsiType?,
initializer: UExpression,
immutable: Boolean,
context: PsiElement?
): ULocalVariable? {
val resolutionFacade = (context as? KtElement)?.getResolutionFacade()
val validator = (context as? KtElement)?.let { usedNamesFilter(it) } ?: { true }
val ktype = resolutionFacade?.let { type?.resolveToKotlinType(it) }
val function = psiFactory.createFunction(
buildString {
append("fun foo() { ")
append(if (immutable) "val" else "var")
append(" ")
append(suggestedName ?: ktype?.let { KotlinNameSuggester.suggestNamesByType(it, validator).firstOrNull() })
?: KotlinNameSuggester.suggestNameByName("v", validator)
ktype?.fqName?.toString()?.let { append(": ").append(it) }
append(" = null")
append("}")
}
)
val ktVariable = PsiTreeUtil.findChildOfType(function, KtVariableDeclaration::class.java)!!
val newVariable = ktVariable.initializer!!.replace(initializer.sourcePsi!!).parent
return newVariable.toUElementOfType<UVariable>() as ULocalVariable
}
@Deprecated("use version with context parameter")
override fun createLocalVariable(
suggestedName: String?,
type: PsiType?,
initializer: UExpression,
immutable: Boolean
): ULocalVariable? {
logger<KotlinUastElementFactory>().error("Please switch caller to the version with a context parameter")
return createLocalVariable(suggestedName, type, initializer, immutable, null)
}
}
private fun usedNamesFilter(context: KtElement): (String) -> Boolean {
val scope = context.getResolutionScope()
return { name: String -> scope.findClassifier(Name.identifier(name), NoLookupLocation.FROM_IDE) == null }
} }
@@ -23,22 +23,26 @@ import org.jetbrains.kotlin.psi.KtConstantExpression
import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry import org.jetbrains.kotlin.psi.KtEscapeStringTemplateEntry
import org.jetbrains.uast.UElement import org.jetbrains.uast.UElement
import org.jetbrains.uast.ULiteralExpression import org.jetbrains.uast.ULiteralExpression
import org.jetbrains.uast.kotlin.internal.KotlinFakeUElement
import org.jetbrains.uast.wrapULiteral
class KotlinULiteralExpression( class KotlinULiteralExpression(
override val sourcePsi: KtConstantExpression, override val sourcePsi: KtConstantExpression,
givenParent: UElement? givenParent: UElement?
) : KotlinAbstractUExpression(givenParent), ULiteralExpression, KotlinUElementWithType, KotlinEvaluatableUElement { ) : KotlinAbstractUExpression(givenParent), ULiteralExpression, KotlinUElementWithType, KotlinEvaluatableUElement, KotlinFakeUElement {
override val isNull: Boolean override val isNull: Boolean
get() = sourcePsi.unwrapBlockOrParenthesis().node?.elementType == KtNodeTypes.NULL get() = sourcePsi.unwrapBlockOrParenthesis().node?.elementType == KtNodeTypes.NULL
override val value by lz { evaluate() } override val value by lz { evaluate() }
override fun unwrapToSourcePsi(): List<PsiElement> = listOfNotNull(wrapULiteral(this).sourcePsi)
} }
class KotlinStringULiteralExpression( class KotlinStringULiteralExpression(
override val sourcePsi: PsiElement, override val sourcePsi: PsiElement,
givenParent: UElement?, givenParent: UElement?,
val text: String val text: String
) : KotlinAbstractUExpression(givenParent), ULiteralExpression, KotlinUElementWithType{ ) : KotlinAbstractUExpression(givenParent), ULiteralExpression, KotlinUElementWithType, KotlinFakeUElement {
constructor(psi: PsiElement, uastParent: UElement?) constructor(psi: PsiElement, uastParent: UElement?)
: this(psi, uastParent, if (psi is KtEscapeStringTemplateEntry) psi.unescapedValue else psi.text) : this(psi, uastParent, if (psi is KtEscapeStringTemplateEntry) psi.unescapedValue else psi.text)
@@ -48,4 +52,6 @@ class KotlinStringULiteralExpression(
override fun evaluate() = value override fun evaluate() = value
override fun getExpressionType(): PsiType? = PsiType.getJavaLangString(sourcePsi.manager, sourcePsi.resolveScope) override fun getExpressionType(): PsiType? = PsiType.getJavaLangString(sourcePsi.manager, sourcePsi.resolveScope)
override fun unwrapToSourcePsi(): List<PsiElement> = listOfNotNull(wrapULiteral(this).sourcePsi)
} }
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.uast.UElement import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UReturnExpression import org.jetbrains.uast.UReturnExpression
import org.jetbrains.uast.kotlin.internal.KotlinFakeUElement
import org.jetbrains.uast.kotlin.internal.toSourcePsiFakeAware
class KotlinUReturnExpression( class KotlinUReturnExpression(
override val sourcePsi: KtReturnExpression, override val sourcePsi: KtReturnExpression,
@@ -31,11 +33,15 @@ class KotlinUReturnExpression(
class KotlinUImplicitReturnExpression( class KotlinUImplicitReturnExpression(
givenParent: UElement? givenParent: UElement?
) : KotlinAbstractUExpression(givenParent), UReturnExpression, KotlinUElementWithType { ) : KotlinAbstractUExpression(givenParent), UReturnExpression, KotlinUElementWithType, KotlinFakeUElement {
override val psi: PsiElement? override val psi: PsiElement?
get() = null get() = null
override lateinit var returnExpression: UExpression override lateinit var returnExpression: UExpression
internal set internal set
override fun unwrapToSourcePsi(): List<PsiElement> {
return returnExpression.toSourcePsiFakeAware()
}
} }
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.uast.kotlin.internal
import com.intellij.psi.PsiElement
import org.jetbrains.uast.UElement
interface KotlinFakeUElement {
fun unwrapToSourcePsi(): List<PsiElement>
}
fun UElement.toSourcePsiFakeAware(): List<PsiElement> {
if (this is KotlinFakeUElement) return this.unwrapToSourcePsi()
return listOfNotNull(this.sourcePsi)
}
@@ -0,0 +1,938 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.uast.test.kotlin
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.Ref
import com.intellij.psi.*
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.UsefulTestCase
import junit.framework.TestCase
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.uast.*
import org.jetbrains.uast.expressions.UInjectionHost
import org.jetbrains.uast.generate.UParameterInfo
import org.jetbrains.uast.generate.UastCodeGenerationPlugin
import org.jetbrains.uast.generate.refreshed
import org.jetbrains.uast.generate.replace
import org.jetbrains.uast.kotlin.generate.KotlinUastElementFactory
import org.jetbrains.uast.test.env.kotlin.findElementByTextFromPsi
import org.jetbrains.uast.test.env.kotlin.findUElementByTextFromPsi
import kotlin.test.fail as kfail
class KotlinUastGenerationTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): LightProjectDescriptor =
KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
private val psiFactory
get() = KtPsiFactory(project)
private val generatePlugin: UastCodeGenerationPlugin
get() = UastCodeGenerationPlugin.byLanguage(KotlinLanguage.INSTANCE)!!
private val uastElementFactory
get() = generatePlugin.getElementFactory(myFixture.project) as KotlinUastElementFactory
fun `test logical and operation with simple operands`() {
val left = psiFactory.createExpression("true").toUElementOfType<UExpression>()
?: kfail("Cannot create left UExpression")
val right = psiFactory.createExpression("false").toUElementOfType<UExpression>()
?: kfail("Cannot create right UExpression")
val expression = uastElementFactory.createBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND, dummyContextFile())
?: kfail("Cannot create expression")
TestCase.assertEquals("true && false", expression.sourcePsi?.text)
}
fun `test logical and operation with simple operands with parenthesis`() {
val left = psiFactory.createExpression("(true)").toUElementOfType<UExpression>()
?: kfail("Cannot create left UExpression")
val right = psiFactory.createExpression("(false)").toUElementOfType<UExpression>()
?: kfail("Cannot create right UExpression")
val expression = uastElementFactory.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND, dummyContextFile())
?: kfail("Cannot create expression")
TestCase.assertEquals("true && false", expression.sourcePsi?.text)
TestCase.assertEquals("""
UBinaryExpression (operator = &&)
ULiteralExpression (value = true)
ULiteralExpression (value = false)
""".trimIndent(), expression.putIntoFunctionBody().asRecursiveLogString().trim())
}
fun `test logical and operation with simple operands with parenthesis polyadic`() {
val left = psiFactory.createExpression("(true && false)").toUElementOfType<UExpression>()
?: kfail("Cannot create left UExpression")
val right = psiFactory.createExpression("(false)").toUElementOfType<UExpression>()
?: kfail("Cannot create right UExpression")
val expression = uastElementFactory.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND, dummyContextFile())
?: kfail("Cannot create expression")
TestCase.assertEquals("true && false && false", expression.sourcePsi?.text)
TestCase.assertEquals("""
UBinaryExpression (operator = &&)
UBinaryExpression (operator = &&)
ULiteralExpression (value = null)
ULiteralExpression (value = null)
ULiteralExpression (value = null)
""".trimIndent(), expression.asRecursiveLogString().trim())
}
fun `test simple reference creating from variable`() {
val context = dummyContextFile()
val variable = uastElementFactory.createLocalVariable(
"a", PsiType.INT, uastElementFactory.createNullLiteral(context), false, context
) ?: kfail("cannot create variable")
val reference = uastElementFactory.createSimpleReference(variable, context) ?: kfail("cannot create reference")
TestCase.assertEquals("a", reference.identifier)
}
fun `test simple reference by name`() {
val reference = uastElementFactory.createSimpleReference("a", dummyContextFile()) ?: kfail("cannot create reference")
TestCase.assertEquals("a", reference.identifier)
}
fun `test parenthesised expression`() {
val expression = psiFactory.createExpression("a + b").toUElementOfType<UExpression>()
?: kfail("cannot create expression")
val parenthesizedExpression = uastElementFactory.createParenthesizedExpression(expression, dummyContextFile())
?: kfail("cannot create parenthesized expression")
TestCase.assertEquals("(a + b)", parenthesizedExpression.sourcePsi?.text)
}
fun `test return expression`() {
val expression = psiFactory.createExpression("a + b").toUElementOfType<UExpression>()
?: kfail("Cannot find plugin")
val returnExpression = uastElementFactory.createReturnExpresion(expression, false, dummyContextFile()) ?: kfail("cannot create return expression")
TestCase.assertEquals("a + b", returnExpression.returnExpression?.asRenderString())
TestCase.assertEquals("return a + b", returnExpression.sourcePsi?.text)
}
fun `test variable declaration without type`() {
val expression = psiFactory.createExpression("1 + 2").toUElementOfType<UExpression>()
?: kfail("cannot create variable declaration")
val declaration = uastElementFactory.createLocalVariable("a", null, expression, false, dummyContextFile()) ?: kfail("cannot create variable")
TestCase.assertEquals("var a = 1 + 2", declaration.sourcePsi?.text)
}
fun `test variable declaration with type`() {
val expression = psiFactory.createExpression("b").toUElementOfType<UExpression>()
?: kfail("cannot create variable declaration")
val declaration = uastElementFactory.createLocalVariable("a", PsiType.DOUBLE, expression, false, dummyContextFile()) ?: kfail("cannot create variable")
TestCase.assertEquals("var a: kotlin.Double = b", declaration.sourcePsi?.text)
}
fun `test final variable declaration`() {
val expression = psiFactory.createExpression("b").toUElementOfType<UExpression>()
?: kfail("cannot create variable declaration")
val declaration = uastElementFactory.createLocalVariable("a", PsiType.DOUBLE, expression, true, dummyContextFile())
?: kfail("cannot create variable")
TestCase.assertEquals("val a: kotlin.Double = b", declaration.sourcePsi?.text)
}
fun `test final variable declaration with unique name`() {
val expression = psiFactory.createExpression("b").toUElementOfType<UExpression>()
?: kfail("cannot create variable declaration")
val declaration = uastElementFactory.createLocalVariable("a", PsiType.DOUBLE, expression, true, dummyContextFile())
?: kfail("cannot create variable")
TestCase.assertEquals("val a: kotlin.Double = b", declaration.sourcePsi?.text)
TestCase.assertEquals("""
ULocalVariable (name = a)
USimpleNameReferenceExpression (identifier = b)
""".trimIndent(), declaration.asRecursiveLogString().trim())
}
fun `test block expression`() {
val statement1 = psiFactory.createExpression("System.out.println()").toUElementOfType<UExpression>()
?: kfail("cannot create statement")
val statement2 = psiFactory.createExpression("System.out.println(2)").toUElementOfType<UExpression>()
?: kfail("cannot create statement")
val block = uastElementFactory.createBlockExpression(listOf(statement1, statement2), dummyContextFile()) ?: kfail("cannot create block")
TestCase.assertEquals("""
{
System.out.println()
System.out.println(2)
}
""".trimIndent(), block.sourcePsi?.text
)
}
fun `test lambda expression`() {
val statement = psiFactory.createExpression("System.out.println()").toUElementOfType<UExpression>()
?: kfail("cannot create statement")
val lambda = uastElementFactory.createLambdaExpression(
listOf(
UParameterInfo(PsiType.INT, "a"),
UParameterInfo(null, "b")
),
statement,
dummyContextFile()
) ?: kfail("cannot create lambda")
TestCase.assertEquals("{ a: kotlin.Int, b -> System.out.println() }", lambda.sourcePsi?.text)
TestCase.assertEquals("""
ULambdaExpression
UParameter (name = a)
UAnnotation (fqName = org.jetbrains.annotations.NotNull)
UParameter (name = b)
UAnnotation (fqName = null)
UBlockExpression
UQualifiedReferenceExpression
UQualifiedReferenceExpression
USimpleNameReferenceExpression (identifier = System)
USimpleNameReferenceExpression (identifier = out)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
""".trimIndent(), lambda.putIntoFunctionBody().asRecursiveLogString().trim())
}
private fun UExpression.putIntoFunctionBody(): UExpression {
val file = myFixture.configureByText("dummyFile.kt", "fun foo() { TODO() }") as KtFile
val ktFunction = file.declarations.single { it.name == "foo" } as KtFunction
val uMethod = ktFunction.toUElementOfType<UMethod>()!!
return runWriteCommand {
uMethod.uastBody.cast<UBlockExpression>().expressions.single().replace(this)!!
}
}
private fun <T : UExpression> T.putIntoVarInitializer(): T {
val file = myFixture.configureByText("dummyFile.kt", "val foo = TODO()") as KtFile
val ktFunction = file.declarations.single { it.name == "foo" } as KtProperty
val uMethod = ktFunction.toUElementOfType<UVariable>()!!
return runWriteCommand {
@Suppress("UNCHECKED_CAST")
generatePlugin.replace(uMethod.uastInitializer!!, this, UExpression::class.java) as T
}
}
private fun <T : UExpression> runWriteCommand(uExpression: () -> T): T {
val result = Ref<T>()
WriteCommandAction.runWriteCommandAction(project) {
result.set(uExpression())
}
return result.get()
}
fun `test lambda expression with explicit types`() {
val statement = psiFactory.createExpression("System.out.println()").toUElementOfType<UExpression>()
?: kfail("cannot create statement")
val lambda = uastElementFactory.createLambdaExpression(
listOf(
UParameterInfo(PsiType.INT, "a"),
UParameterInfo(PsiType.DOUBLE, "b")
),
statement,
dummyContextFile()
) ?: kfail("cannot create lambda")
TestCase.assertEquals("{ a: kotlin.Int, b: kotlin.Double -> System.out.println() }", lambda.sourcePsi?.text)
TestCase.assertEquals("""
ULambdaExpression
UParameter (name = a)
UAnnotation (fqName = org.jetbrains.annotations.NotNull)
UParameter (name = b)
UAnnotation (fqName = org.jetbrains.annotations.NotNull)
UBlockExpression
UQualifiedReferenceExpression
UQualifiedReferenceExpression
USimpleNameReferenceExpression (identifier = System)
USimpleNameReferenceExpression (identifier = out)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
""".trimIndent(), lambda.putIntoFunctionBody().asRecursiveLogString().trim())
}
fun `test lambda expression with simplified block body with context`() {
val r = psiFactory.createExpression("return \"10\"").toUElementOfType<UExpression>()
?: kfail("cannot create return")
val block = uastElementFactory.createBlockExpression(listOf(r), dummyContextFile()) ?: kfail("cannot create block")
val lambda = uastElementFactory.createLambdaExpression(listOf(UParameterInfo(null, "a")), block, dummyContextFile())
?: kfail("cannot create lambda")
TestCase.assertEquals("""{ a -> "10" }""".trimMargin(), lambda.sourcePsi?.text)
TestCase.assertEquals("""
ULambdaExpression
UParameter (name = a)
UAnnotation (fqName = org.jetbrains.annotations.NotNull)
UBlockExpression
UReturnExpression
ULiteralExpression (value = "10")
""".trimIndent(), lambda.putIntoVarInitializer().asRecursiveLogString().trim())
}
fun `test function argument replacement`() {
val file = myFixture.configureByText(
"test.kt", """
fun f(a: Any){}
fun main(){
f(a)
}
""".trimIndent()
)
val expression = file.findUElementByTextFromPsi<UCallExpression>("f(a)")
val newArgument = psiFactory.createExpression("b").toUElementOfType<USimpleNameReferenceExpression>()
?: kfail("cannot create reference")
WriteCommandAction.runWriteCommandAction(project) {
TestCase.assertNotNull(expression.valueArguments[0].replace(newArgument))
}
val updated = expression.refreshed() ?: kfail("cannot update expression")
TestCase.assertEquals("f", updated.methodName)
TestCase.assertTrue(updated.valueArguments[0] is USimpleNameReferenceExpression)
TestCase.assertEquals("b", (updated.valueArguments[0] as USimpleNameReferenceExpression).identifier)
TestCase.assertEquals("""
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (f))
USimpleNameReferenceExpression (identifier = f, resolvesTo = null)
USimpleNameReferenceExpression (identifier = b)
""".trimIndent(), updated.asRecursiveLogString().trim())
}
fun `test suggested name`() {
val expression = psiFactory.createExpression("f(a) + 1").toUElementOfType<UExpression>()
?: kfail("cannot create expression")
val variable = uastElementFactory.createLocalVariable(null, PsiType.INT, expression, true, dummyContextFile())
?: kfail("cannot create variable")
TestCase.assertEquals("val i: kotlin.Int = f(a) + 1", variable.sourcePsi?.text)
TestCase.assertEquals("""
ULocalVariable (name = i)
UBinaryExpression (operator = +)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (f))
USimpleNameReferenceExpression (identifier = <anonymous class>, resolvesTo = null)
USimpleNameReferenceExpression (identifier = a)
ULiteralExpression (value = null)
""".trimIndent(), variable.asRecursiveLogString().trim())
}
fun `test method call generation with receiver`() {
val receiver = psiFactory.createExpression(""""10"""").toUElementOfType<UExpression>()
?: kfail("cannot create receiver")
val arg1 = psiFactory.createExpression("1").toUElementOfType<UExpression>()
?: kfail("cannot create arg1")
val arg2 = psiFactory.createExpression("2").toUElementOfType<UExpression>()
?: kfail("cannot create arg2")
val methodCall = uastElementFactory.createCallExpression(
receiver,
"substring",
listOf(arg1, arg2),
null,
UastCallKind.METHOD_CALL
) ?: kfail("cannot create call")
TestCase.assertEquals(""""10".substring(1,2)""", methodCall.uastParent?.sourcePsi?.text)
TestCase.assertEquals("""
UQualifiedReferenceExpression
ULiteralExpression (value = "10")
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 2))
UIdentifier (Identifier (substring))
USimpleNameReferenceExpression (identifier = <anonymous class>, resolvesTo = null)
ULiteralExpression (value = null)
ULiteralExpression (value = null)
""".trimIndent(), methodCall.uastParent?.asRecursiveLogString()?.trim()
)
}
fun `test method call generation without receiver`() {
val arg1 = psiFactory.createExpression("1").toUElementOfType<UExpression>()
?: kfail("cannot create arg1")
val arg2 = psiFactory.createExpression("2").toUElementOfType<UExpression>()
?: kfail("cannot create arg2")
val methodCall = uastElementFactory.createCallExpression(
null,
"substring",
listOf(arg1, arg2),
null,
UastCallKind.METHOD_CALL
) ?: kfail("cannot create call")
TestCase.assertEquals("""substring(1,2)""", methodCall.sourcePsi?.text)
}
fun `test method call generation with generics restoring`() {
val arrays = psiFactory.createExpression("java.util.Arrays").toUElementOfType<UExpression>()
?: kfail("cannot create receiver")
val methodCall = uastElementFactory.createCallExpression(
arrays,
"asList",
listOf(),
createTypeFromText("java.util.List<java.lang.String>", null),
UastCallKind.METHOD_CALL,
dummyContextFile()
) ?: kfail("cannot create call")
TestCase.assertEquals("java.util.Arrays.asList<kotlin.String>()", methodCall.uastParent?.sourcePsi?.text)
}
fun `test method call generation with generics restoring 2 parameters`() {
val collections = psiFactory.createExpression("java.util.Collections").toUElementOfType<UExpression>()
?: kfail("cannot create receiver")
TestCase.assertEquals("java.util.Collections", collections.asRenderString())
val methodCall = uastElementFactory.createCallExpression(
collections,
"emptyMap",
listOf(),
createTypeFromText(
"java.util.Map<java.lang.String, java.lang.Integer>",
null
),
UastCallKind.METHOD_CALL,
dummyContextFile()
) ?: kfail("cannot create call")
TestCase.assertEquals("emptyMap<kotlin.String, kotlin.Int>()", methodCall.sourcePsi?.text)
TestCase.assertEquals("java.util.Collections.emptyMap<kotlin.String, kotlin.Int>()", methodCall.sourcePsi?.parent?.text)
TestCase.assertEquals(
"""
UQualifiedReferenceExpression
UQualifiedReferenceExpression
UQualifiedReferenceExpression
USimpleNameReferenceExpression (identifier = java)
USimpleNameReferenceExpression (identifier = util)
USimpleNameReferenceExpression (identifier = Collections)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))
UIdentifier (Identifier (emptyMap))
USimpleNameReferenceExpression (identifier = <anonymous class>, resolvesTo = null)
""".trimIndent(), methodCall.uastParent?.asRecursiveLogString()?.trim()
)
}
private fun dummyContextFile(): KtFile = myFixture.configureByText("file.kt", "fun foo() {}") as KtFile
fun `test method call generation with generics restoring 1 parameter with 1 existing`() {
val a = psiFactory.createExpression("A").toUElementOfType<UExpression>()
?: kfail("cannot create a receiver")
val param = psiFactory.createExpression("\"a\"").toUElementOfType<UExpression>()
?: kfail("cannot create a parameter")
val methodCall = uastElementFactory.createCallExpression(
a,
"kek",
listOf(param),
createTypeFromText(
"java.util.Map<java.lang.String, java.lang.Integer>",
null
),
UastCallKind.METHOD_CALL,
dummyContextFile()
) ?: kfail("cannot create call")
TestCase.assertEquals("A.kek<kotlin.String, kotlin.Int>(\"a\")", methodCall.sourcePsi?.parent?.text)
TestCase.assertEquals("""
UQualifiedReferenceExpression
USimpleNameReferenceExpression (identifier = A)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (kek))
USimpleNameReferenceExpression (identifier = <anonymous class>, resolvesTo = null)
ULiteralExpression (value = "a")
""".trimIndent(), methodCall.uastParent?.asRecursiveLogString()?.trim())
}
//not implemented (currently we dont perform resolve in code generating)
fun `ignore method call generation with generics restoring 1 parameter with 1 unused `() {
val aClassFile = myFixture.configureByText("A.kt",
"""
object A {
fun <T1, T2, T3> kek(a: T1): Map<T1, T3> {
return TODO();
}
}
""".trimIndent()
)
val a = psiFactory.createExpression("A").toUElementOfType<UExpression>()
?: kfail("cannot create a receiver")
val param = psiFactory.createExpression("\"a\"").toUElementOfType<UExpression>()
?: kfail("cannot create a parameter")
val methodCall = uastElementFactory.createCallExpression(
a,
"kek",
listOf(param),
createTypeFromText(
"java.util.Map<java.lang.String, java.lang.Integer>",
null
),
UastCallKind.METHOD_CALL,
aClassFile
) ?: kfail("cannot create call")
TestCase.assertEquals("A.<String, Object, Integer>kek(\"a\")", methodCall.sourcePsi?.text)
}
fun `test method call generation with generics with context`() {
val file = myFixture.configureByText("file.kt", """
class A {
fun <T> method(): List<T> { TODO() }
}
fun main(){
val a = A()
println(a)
}
""".trimIndent()
) as KtFile
val reference = file.findUElementByTextFromPsi<UElement>("println(a)")
.findElementByTextFromPsi<UReferenceExpression>("a")
val callExpression = uastElementFactory.createCallExpression(
reference,
"method",
emptyList(),
createTypeFromText(
"java.util.List<java.lang.Integer>",
null
),
UastCallKind.METHOD_CALL,
context = reference.sourcePsi
) ?: kfail("cannot create method call")
TestCase.assertEquals("a.method<kotlin.Int>()", callExpression.uastParent?.sourcePsi?.text)
TestCase.assertEquals("""
UQualifiedReferenceExpression
USimpleNameReferenceExpression (identifier = a)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 0))
UIdentifier (Identifier (method))
USimpleNameReferenceExpression (identifier = <anonymous class>, resolvesTo = null)
""".trimIndent(), callExpression.uastParent?.asRecursiveLogString()?.trim()
)
}
fun `test replace lambda implicit return value`() {
val file = myFixture.configureByText(
"file.kt", """
fun main(){
val a: (Int) -> String = {
println(it)
println(2)
"abc"
}
}
""".trimIndent()
) as KtFile
val uLambdaExpression = file.findUElementByTextFromPsi<UInjectionHost>("\"abc\"")
.getParentOfType<ULambdaExpression>() ?: kfail("cant get lambda")
val expressions = uLambdaExpression.body.cast<UBlockExpression>().expressions
UsefulTestCase.assertSize(3, expressions)
val uReturnExpression = expressions.last() as UReturnExpression
val newStringLiteral = uastElementFactory.createStringLiteralExpression("def", file) ?: kfail("cannot create method call")
val defReturn = runWriteCommand { uReturnExpression.replace(newStringLiteral) ?: kfail("cant replace") }
val uLambdaExpression2 = defReturn.getParentOfType<ULambdaExpression>() ?: kfail("cant get lambda")
TestCase.assertEquals("{\n println(it)\n println(2)\n \"def\"\n }", uLambdaExpression2.sourcePsi?.text)
TestCase.assertEquals(
"""
ULambdaExpression
UParameter (name = it)
UBlockExpression
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
USimpleNameReferenceExpression (identifier = it)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
ULiteralExpression (value = 2)
UReturnExpression
ULiteralExpression (value = "def")
""".trimIndent(), uLambdaExpression2.asRecursiveLogString().trim()
)
}
private class UserDataChecker {
private val storedData = Any()
private val KEY = Key.create<Any>("testKey")
private lateinit var uniqueStringLiteralText: String
fun store(uElement: UInjectionHost) {
val psiElement = uElement.sourcePsi as KtStringTemplateExpression
uniqueStringLiteralText = psiElement.text
psiElement.putCopyableUserData(KEY, storedData)
}
fun checkUserDataAlive(uElement: UElement) {
val psiElements = uElement.let { SyntaxTraverser.psiTraverser(it.sourcePsi) }
.filter(KtStringTemplateExpression::class.java)
.filter { it.text == uniqueStringLiteralText }.toList()
UsefulTestCase.assertNotEmpty(psiElements)
UsefulTestCase.assertTrue("uElement still should keep the userdata", psiElements.any { storedData === it!!.getCopyableUserData(KEY) })
}
}
fun `test add intermediate returns to lambda`() {
val file = myFixture.configureByText(
"file.kt", """
fun main(){
val a: (Int) -> String = lname@{
println(it)
println(2)
"abc"
}
}
""".trimIndent()
) as KtFile
val aliveChecker = UserDataChecker()
val uLambdaExpression = file.findUElementByTextFromPsi<UInjectionHost>("\"abc\"")
.also(aliveChecker::store)
.getParentOfType<ULambdaExpression>() ?: kfail("cant get lambda")
val oldBlockExpression = uLambdaExpression.body.cast<UBlockExpression>()
UsefulTestCase.assertSize(3, oldBlockExpression.expressions)
val conditionalExit = with(uastElementFactory) {
createIfExpression(
createBinaryExpression(
createSimpleReference("it", uLambdaExpression.sourcePsi)!!,
createIntLiteral(3, uLambdaExpression.sourcePsi),
UastBinaryOperator.GREATER,
uLambdaExpression.sourcePsi
)!!,
createReturnExpresion(
createStringLiteralExpression("exit", uLambdaExpression.sourcePsi)!!, true,
uLambdaExpression.sourcePsi
)!!,
null,
uLambdaExpression.sourcePsi
)!!
}
val newBlockExpression = uastElementFactory.createBlockExpression(
listOf(conditionalExit) + oldBlockExpression.expressions,
uLambdaExpression.sourcePsi
)!!
aliveChecker.checkUserDataAlive(newBlockExpression)
val uLambdaExpression2 = runWriteCommand {
oldBlockExpression.replace(newBlockExpression) ?: kfail("cant replace")
}.getParentOfType<ULambdaExpression>() ?: kfail("cant get lambda")
aliveChecker.checkUserDataAlive(uLambdaExpression2)
TestCase.assertEquals(
"""
lname@{
if (it > 3) return@lname "exit"
println(it)
println(2)
"abc"
}
""".trimIndent(), uLambdaExpression2.sourcePsi?.parent?.text
)
TestCase.assertEquals(
"""
ULambdaExpression
UParameter (name = it)
UBlockExpression
UIfExpression
UBinaryExpression (operator = >)
USimpleNameReferenceExpression (identifier = it)
ULiteralExpression (value = 3)
UReturnExpression
ULiteralExpression (value = "exit")
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
USimpleNameReferenceExpression (identifier = it)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
ULiteralExpression (value = 2)
UReturnExpression
ULiteralExpression (value = "abc")
""".trimIndent(), uLambdaExpression2.asRecursiveLogString().trim()
)
}
fun `test converting lambda to if`() {
val file = myFixture.configureByText(
"file.kt", """
fun foo(call: (Int) -> String): String = call.invoke(2)
fun main() {
foo {
println(it)
println(2)
"abc"
}
}
}
""".trimIndent()
) as KtFile
val aliveChecker = UserDataChecker()
val uLambdaExpression = file.findUElementByTextFromPsi<UInjectionHost>("\"abc\"")
.also { aliveChecker.store(it) }
.getParentOfType<ULambdaExpression>() ?: kfail("cant get lambda")
val oldBlockExpression = uLambdaExpression.body.cast<UBlockExpression>()
aliveChecker.checkUserDataAlive(oldBlockExpression)
val newLambda = with(uastElementFactory) {
createLambdaExpression(
listOf(UParameterInfo(null, "it")),
createIfExpression(
createBinaryExpression(
createSimpleReference("it", uLambdaExpression.sourcePsi)!!,
createIntLiteral(3, uLambdaExpression.sourcePsi),
UastBinaryOperator.GREATER,
uLambdaExpression.sourcePsi
)!!,
oldBlockExpression,
createReturnExpresion(
createStringLiteralExpression("exit", uLambdaExpression.sourcePsi)!!, true,
uLambdaExpression.sourcePsi
)!!,
uLambdaExpression.sourcePsi
)!!.also {
aliveChecker.checkUserDataAlive(it)
},
uLambdaExpression.sourcePsi
)!!
}
aliveChecker.checkUserDataAlive(newLambda)
val uLambdaExpression2 = runWriteCommand {
uLambdaExpression.replace(newLambda) ?: kfail("cant replace")
}
TestCase.assertEquals(
"""
{ it ->
if (it > 3) {
println(it)
println(2)
"abc"
} else return@foo "exit"
}
""".trimIndent(), uLambdaExpression2.sourcePsi?.parent?.text
)
TestCase.assertEquals(
"""
ULambdaExpression
UParameter (name = it)
UAnnotation (fqName = null)
UBlockExpression
UReturnExpression
UIfExpression
UBinaryExpression (operator = >)
USimpleNameReferenceExpression (identifier = it)
ULiteralExpression (value = 3)
UBlockExpression
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
USimpleNameReferenceExpression (identifier = it)
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
ULiteralExpression (value = 2)
ULiteralExpression (value = "abc")
UReturnExpression
ULiteralExpression (value = "exit")
""".trimIndent(), uLambdaExpression2.asRecursiveLogString().trim()
)
aliveChecker.checkUserDataAlive(uLambdaExpression2)
}
fun `test removing unnecessary type parameters while replace`() {
val aClassFile = myFixture.configureByText(
"A.kt",
"""
class A {
fun <T> method():List<T> = TODO()
}
""".trimIndent()
)
val reference = psiFactory.createExpression("a")
.toUElementOfType<UReferenceExpression>() ?: kfail("cannot create reference expression")
val callExpression = uastElementFactory.createCallExpression(
reference,
"method",
emptyList(),
createTypeFromText(
"java.util.List<java.lang.Integer>",
null
),
UastCallKind.METHOD_CALL,
context = aClassFile
) ?: kfail("cannot create method call")
val listAssigment = myFixture.addFileToProject("temp.kt", """
fun foo(kek: List<Int>) {
val list: List<Int> = kek
}
""".trimIndent()).findUElementByTextFromPsi<UVariable>("val list: List<Int> = kek")
WriteCommandAction.runWriteCommandAction(project) {
val methodCall = listAssigment.uastInitializer?.replace(callExpression) ?: kfail("cannot replace!")
// originally result expected be `a.method()` but we expect to clean up type arguments in other plase
TestCase.assertEquals("a.method<Int>()", methodCall.sourcePsi?.parent?.text)
}
}
fun `test create if`() {
val condition = psiFactory.createExpression("true").toUElementOfType<UExpression>()
?: kfail("cannot create condition")
val thenBranch = psiFactory.createBlock("{a(b);}").toUElementOfType<UExpression>()
?: kfail("cannot create then branch")
val elseBranch = psiFactory.createExpression("c++").toUElementOfType<UExpression>()
?: kfail("cannot create else branch")
val ifExpression = uastElementFactory.createIfExpression(condition, thenBranch, elseBranch, dummyContextFile())
?: kfail("cannot create if expression")
TestCase.assertEquals("if (true) {\n { a(b); }\n } else c++", ifExpression.sourcePsi?.text)
}
fun `test qualified reference`() {
val reference = uastElementFactory.createQualifiedReference("java.util.List", myFixture.file)
TestCase.assertEquals("java.util.List", reference?.sourcePsi?.text)
}
fun `test build lambda from returning a variable`() {
val context = dummyContextFile()
val localVariable = uastElementFactory.createLocalVariable("a", null, uastElementFactory.createNullLiteral(context), true, context)
?: kfail("cannot create variable")
val declarationExpression =
uastElementFactory.createDeclarationExpression(listOf(localVariable), context) ?: kfail("cannot create declaration expression")
val returnExpression = uastElementFactory.createReturnExpresion(
uastElementFactory.createSimpleReference(localVariable, context), false, context
) ?: kfail("cannot create return expression")
val block = uastElementFactory.createBlockExpression(listOf(declarationExpression, returnExpression), context)
?: kfail("cannot create block expression")
TestCase.assertEquals("""
UBlockExpression
UDeclarationsExpression
ULocalVariable (name = a)
ULiteralExpression (value = null)
UReturnExpression
USimpleNameReferenceExpression (identifier = a)
""".trimIndent(), block.asRecursiveLogString().trim())
val lambda = uastElementFactory.createLambdaExpression(listOf(), block, context) ?: kfail("cannot create lambda expression")
TestCase.assertEquals("{ val a = null\na }", lambda.sourcePsi?.text)
TestCase.assertEquals("""
ULambdaExpression
UBlockExpression
UDeclarationsExpression
ULocalVariable (name = a)
ULiteralExpression (value = null)
UReturnExpression
USimpleNameReferenceExpression (identifier = a)
""".trimIndent(), lambda.putIntoVarInitializer().asRecursiveLogString().trim())
}
fun `test expand oneline lambda`() {
val context = dummyContextFile()
val parameters = listOf(UParameterInfo(PsiType.INT, "a"))
val oneLineLambda = with(uastElementFactory) {
createLambdaExpression(
parameters,
createBinaryExpression(
createSimpleReference("a", context)!!,
createSimpleReference("a", context)!!,
UastBinaryOperator.PLUS, context
)!!, context
)!!
}.putIntoVarInitializer()
val lambdaReturn = (oneLineLambda.body as UBlockExpression).expressions.single()
val lambda = with(uastElementFactory) {
createLambdaExpression(
parameters,
createBlockExpression(
listOf(
createCallExpression(
null,
"println",
listOf(createSimpleReference("a", context)!!),
PsiType.VOID,
UastCallKind.METHOD_CALL,
context
)!!,
lambdaReturn
),
context
)!!, context
)!!
}
TestCase.assertEquals("{ a: kotlin.Int -> println(a)\na + a }", lambda.sourcePsi?.text)
TestCase.assertEquals("""
ULambdaExpression
UParameter (name = a)
UAnnotation (fqName = org.jetbrains.annotations.NotNull)
UBlockExpression
UCallExpression (kind = UastCallKind(name='method_call'), argCount = 1))
UIdentifier (Identifier (println))
USimpleNameReferenceExpression (identifier = println, resolvesTo = null)
USimpleNameReferenceExpression (identifier = a)
UReturnExpression
UBinaryExpression (operator = +)
USimpleNameReferenceExpression (identifier = a)
USimpleNameReferenceExpression (identifier = a)
""".trimIndent(), lambda.putIntoVarInitializer().asRecursiveLogString().trim())
}
private fun createTypeFromText(s: String, newClass: PsiElement?): PsiType? {
return JavaPsiFacade.getElementFactory(myFixture.project).createTypeFromText(s, newClass)
}
}