From 876df1daa5c2fe43ee31814d98d4f20520551e3c Mon Sep 17 00:00:00 2001 From: "aleksandr.izmaylov" Date: Wed, 31 Jul 2019 10:42:37 +0300 Subject: [PATCH] [uast] generating method calls in java, with restoring generics type parameters,if generation, smart replace IDEA-CR-50955 GitOrigin-RevId: a42242a1c30c2d7b9d1ee19881b0ba4d16aee4fa --- .../uast/generate/UastCodeGenerationPlugin.kt | 26 +- .../generate/JavaUastCodeGenerationPlugin.kt | 300 ++++++++++++++---- .../java/generate/JavaUastGenerationTest.kt | 183 ++++++++++- 3 files changed, 437 insertions(+), 72 deletions(-) diff --git a/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt b/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt index 15432ea3cd91..e6b5a156ea44 100644 --- a/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt +++ b/uast/uast-common/src/org/jetbrains/uast/generate/UastCodeGenerationPlugin.kt @@ -38,19 +38,34 @@ interface UastCodeGenerationPlugin { fun createReturnExpresion(expression: UExpression, inLambda: Boolean = false): UReturnExpression? - fun createLocalVariable(name: String?, type: PsiType?, initializer: UExpression, immutable: Boolean = false): ULocalVariable? + fun createLocalVariable(suggestedName: String?, type: PsiType?, initializer: UExpression, immutable: Boolean = false): ULocalVariable? fun createBlockExpression(expressions: List, project: Project): UBlockExpression? - fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? + fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? fun createDeclarationExpression(declarations: List, project: Project): UDeclarationsExpression? - fun replace(oldElement: UElement, newElement: T, elementType: Class): T? + fun createCallExpression(receiver: UExpression?, + methodName: String, + parameters: List, + project: Project, + expectedReturnType: PsiType?, + kind: UastCallKind): UCallExpression? + + fun createIfExpression(condition: UExpression, thenBranch: UExpression, elseBranch: UExpression? = null): UIfExpression? + + fun replace(oldElement: UElement, newElement: T, elementType: Class): T? } @ApiStatus.Experimental -inline fun UElement.replace(newElement: T): T? = +data class UParameterInfo(val type: PsiType?, val suggestedName: String?) + +@ApiStatus.Experimental +infix fun String?.ofType(type: PsiType?): UParameterInfo = UParameterInfo(type, this) + +@ApiStatus.Experimental +inline fun UElement.replace(newElement: T): T? = this.getLanguagePlugin() .let { UastCodeGenerationPlugin.byLanguage(it.language) } ?.replace(this, newElement, T::class.java) @@ -60,4 +75,5 @@ inline fun T.refreshed() = sourcePsi?.toUElementOfType val UElement.generationPlugin: UastCodeGenerationPlugin? @ApiStatus.Experimental - get() = getLanguagePlugin().let { UastCodeGenerationPlugin.byLanguage(it.language) } \ No newline at end of file + get() = getLanguagePlugin().let { UastCodeGenerationPlugin.byLanguage(it.language) } + diff --git a/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt b/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt index f3c7b3e6d0ee..aa50a108cc23 100644 --- a/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt +++ b/uast/uast-java/src/org/jetbrains/uast/java/generate/JavaUastCodeGenerationPlugin.kt @@ -10,27 +10,172 @@ import com.intellij.psi.* import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.VariableKind import com.intellij.psi.impl.source.tree.CompositeElement +import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.PsiUtil +import com.intellij.util.castSafelyTo import com.siyeh.ig.psiutils.ParenthesesUtils import org.jetbrains.uast.* +import org.jetbrains.uast.generate.UParameterInfo import org.jetbrains.uast.generate.UastCodeGenerationPlugin import org.jetbrains.uast.java.* -import java.lang.AssertionError class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { - override fun replace(oldElement: UElement, newElement: T, elementType: Class): T? = - newElement.sourcePsi?.let { oldElement.sourcePsi?.replace(it) }?.toUElementOfExpectedTypes(elementType) + override fun createIfExpression(condition: UExpression, thenBranch: UExpression, elseBranch: UExpression?): UIfExpression? { + val conditionPsi = condition.sourcePsi ?: return null + val thenBranchPsi = thenBranch.sourcePsi ?: return null + val factory = JavaPsiFacade.getElementFactory(conditionPsi.project) + + val ifStatement = factory.createStatementFromText("if (a) b else c", null) as? PsiIfStatement ?: return null + ifStatement.condition?.replace(conditionPsi) + + ifStatement.thenBranch?.replace(thenBranchPsi.branchStatement ?: return null) + elseBranch?.sourcePsi?.branchStatement?.let { ifStatement.elseBranch?.replace(it) } ?: ifStatement.elseBranch?.delete() + + return JavaUIfExpression(ifStatement, null) + } + + override fun createCallExpression(receiver: UExpression?, + methodName: String, + parameters: List, + project: Project, + expectedReturnType: PsiType?, + kind: UastCallKind): UCallExpression? { + if (kind != UastCallKind.METHOD_CALL) return null + + val factory = JavaPsiFacade.getElementFactory(project) + val methodCall = factory.createExpressionFromText(if (receiver != null) "a.b()" else "a()", null) as? PsiMethodCallExpression + ?: return null + + val methodIdentifier = factory.createIdentifier(methodName) + + if (receiver != null) { + methodCall.methodExpression.qualifierExpression?.replace(receiver.sourcePsi!!) + } + methodCall.methodExpression.referenceNameElement?.replace(methodIdentifier) + + for (parameter in parameters) { + methodCall.argumentList.add(parameter.sourcePsi!!) + } + + return if (expectedReturnType == null) + JavaUCallExpression(methodCall, null) + else + MethodCallUpgradeHelper(project, methodCall, expectedReturnType).tryUpgradeToExpectedType() + ?.let { JavaUCallExpression(it, null) } + } + + private class MethodCallUpgradeHelper(val project: Project, val methodCall: PsiMethodCallExpression, val expectedReturnType: PsiType) { + lateinit var resultType: PsiType + + fun tryUpgradeToExpectedType(): PsiMethodCallExpression? { + resultType = methodCall.type ?: return null + + if (expectedReturnType.isAssignableFrom(resultType)) return methodCall + + if (!(resultType eqResolved expectedReturnType)) + return null + + return methodCall.methodExpression.qualifierExpression?.let { tryPickUpTypeParameters() } + } + + private fun tryPickUpTypeParameters(): PsiMethodCallExpression? { + val expectedTypeTypeParameters = expectedReturnType.castSafelyTo()?.parameters ?: return null + val resultTypeTypeParameters = resultType.castSafelyTo()?.parameters ?: return null + val notEqualTypeParametersIndices = expectedTypeTypeParameters + .zip(resultTypeTypeParameters) + .withIndex() + .filterNot { (_, pair) -> PsiTypesUtil.compareTypes(pair.first, pair.second, false) } + .map { (i, _) -> i } + + val resolvedMethod = methodCall.resolveMethod() ?: return null + val methodReturnTypeTypeParameters = (resolvedMethod.returnType.castSafelyTo())?.parameters ?: return null + val methodDefinitionTypeParameters = resolvedMethod.typeParameters + val methodDefinitionToReturnTypeParametersMapping = methodDefinitionTypeParameters.map { + it to methodReturnTypeTypeParameters.indexOfFirst { param -> it.name == param.canonicalText } + } + .filter { it.second != -1 } + .toMap() + + val notMatchedTypesParametersNames = notEqualTypeParametersIndices + .map { methodReturnTypeTypeParameters[it].canonicalText } + .toSet() + + val callTypeParametersSubstitutor = methodCall.resolveMethodGenerics().substitutor + val newParametersList = mutableListOf() + for (parameter in methodDefinitionTypeParameters) { + if (parameter.name in notMatchedTypesParametersNames) { + newParametersList += expectedTypeTypeParameters[methodDefinitionToReturnTypeParametersMapping[parameter] ?: return null] + } + else { + newParametersList += callTypeParametersSubstitutor.substitute(parameter) ?: return null + } + } + + return buildMethodCallFromNewTypeParameters(newParametersList) + } + + private fun buildMethodCallFromNewTypeParameters(newParametersList: List): PsiMethodCallExpression? { + val factory = JavaPsiFacade.getElementFactory(project) + val expr = factory.createExpressionFromText(buildString { + append("a.<") + newParametersList.joinTo(this) { "T" } + append(">b()") + }, null) as? PsiMethodCallExpression ?: return null + + for ((i, param) in newParametersList.withIndex()) { + val typeElement = factory.createTypeElement(param) + expr.typeArgumentList.typeParameterElements[i].replace(typeElement) + } + + methodCall.typeArgumentList.replace(expr.typeArgumentList) + + if (methodCall.type?.let { expectedReturnType.isAssignableFrom(it) } != true) + return null + + return methodCall + } + + infix fun PsiType.eqResolved(other: PsiType): Boolean { + val resolvedThis = this.castSafelyTo()?.resolve() ?: return false + val resolvedOther = other.castSafelyTo()?.resolve() ?: return false + + return PsiManager.getInstance(project).areElementsEquivalent(resolvedThis, resolvedOther) + } + } + + private fun adjustChainStyleToMethodCalls(oldPsi: PsiElement, newPsi: PsiElement) { + if (oldPsi is PsiMethodCallExpression && newPsi is PsiMethodCallExpression && + oldPsi.methodExpression.qualifierExpression != null && newPsi.methodExpression.qualifier != null) { + + if (oldPsi.methodExpression.children.getOrNull(1) is PsiWhiteSpace && + newPsi.methodExpression.children.getOrNull(1) !is PsiWhiteSpace + ) { + newPsi.methodExpression.addAfter(oldPsi.methodExpression.children[1], newPsi.methodExpression.children[0]) + } + } + } + + override fun replace(oldElement: UElement, newElement: T, elementType: Class): T? { + val oldPsi = oldElement.sourcePsi ?: return null + val newPsi = newElement.sourcePsi ?: return null + + adjustChainStyleToMethodCalls(oldPsi, newPsi) + + val factory = JavaPsiFacade.getElementFactory(oldPsi.project) + return if (oldPsi is PsiStatement && newPsi is PsiExpression) { + newPsi.let { factory.createExpresionStatement(it) } + } + else { + newPsi + } + ?.let { oldPsi.replace(it) } + ?.toUElementOfExpectedTypes(elementType) + } override fun createDeclarationExpression(declarations: List, project: Project): UDeclarationsExpression? { return JavaUDeclarationsExpression(null, declarations) } - private fun PsiElementFactory.createExpresionStatement(expression: PsiExpression): PsiStatement? { - val statement = createStatementFromText("x;", null) as? PsiExpressionStatement ?: return null - statement.expression.replace(expression) - return statement - } - override fun createReturnExpresion(expression: UExpression, inLambda: Boolean): UReturnExpression? { val factory = JavaPsiFacade.getElementFactory(expression.sourcePsi?.project ?: return null) val returnStatement = factory.createStatementFromText("return ;", null) as? PsiReturnStatement ?: return null @@ -40,40 +185,38 @@ class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { return JavaUReturnExpression(returnStatement, null) } - override fun createLocalVariable(name: String?, type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? { - val initializerPsi = initializer.sourcePsi as? PsiExpression ?: return null - - if (name == null) { - return createLocalVariableWithSuggestedName(type, initializer, immutable) + private fun PsiVariable.setMutability(immutable: Boolean) { + if (immutable && !this.hasModifier(JvmModifier.FINAL)) { + PsiUtil.setModifierProperty(this, PsiModifier.FINAL, true) } + if (!immutable && this.hasModifier(JvmModifier.FINAL)) { + PsiUtil.setModifierProperty(this, PsiModifier.FINAL, false) + } + } + + override fun createLocalVariable(suggestedName: String?, type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? { + val initializerPsi = initializer.sourcePsi as? PsiExpression ?: return null + + val name = createNameFromSuggested( + suggestedName, + initializerPsi.project, + VariableKind.LOCAL_VARIABLE, + type, + initializer = initializerPsi, + context = initializerPsi + ) ?: return null val factory = JavaPsiFacade.getElementFactory(initializerPsi.project) val variable = (type ?: initializer.getExpressionType())?.let { variableType -> factory.createVariableDeclarationStatement(name, variableType, initializerPsi, initializerPsi.context).declaredElements.firstOrNull() as? PsiLocalVariable } ?: return null - if (immutable && !variable.hasModifier(JvmModifier.FINAL)) { - PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true) - } - - if (!immutable && variable.hasModifier(JvmModifier.FINAL)) { - PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, false) - } + variable.setMutability(immutable) return JavaULocalVariable(variable, null) } - private fun createLocalVariableWithSuggestedName(type: PsiType?, initializer: UExpression, immutable: Boolean): ULocalVariable? { - val initializerPsi = initializer.sourcePsi as? PsiExpression ?: return null - val codeStyleManager = JavaCodeStyleManager.getInstance(initializerPsi.project) - val resultType = type ?: initializerPsi.type ?: return null - var suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, initializerPsi, resultType) - suggestedNameInfo = codeStyleManager.suggestUniqueVariableName(suggestedNameInfo, initializerPsi, true) - val name = suggestedNameInfo.names.firstOrNull() ?: return null - return createLocalVariable(name, type, initializer, immutable) - } - override fun createBlockExpression(expressions: List, project: Project): UBlockExpression? { val factory = JavaPsiFacade.getElementFactory(project) val blockStatement = BlockUtils.createBlockStatement(project) @@ -96,30 +239,41 @@ class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { return JavaUBlockExpression(blockStatement, null) } - override fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? { - val factory = JavaPsiFacade.getElementFactory(body.sourcePsi?.project ?: return null) + override fun createLambdaExpression(parameters: List, body: UExpression): ULambdaExpression? { + //TODO: smart handling cases, when parameters types should exist in code + val project = body.sourcePsi?.project ?: return null + val factory = JavaPsiFacade.getElementFactory(project) val lambda = factory.createExpressionFromText( buildString { - parameters.joinTo(this, separator = ",", prefix = "(", postfix = ")") { "x" } - - append("->") - append("{}") + parameters.joinTo(this, separator = ",", prefix = "(", postfix = ")") { "x x" } + append("->{}") }, null ) as? PsiLambdaExpression ?: return null - lambda.parameterList.parameters.forEachIndexed { i, p -> - p.replace(parameters[i].sourcePsi ?: return null) + val needsType = parameters.all { it.type != null } + lambda.parameterList.parameters.forEachIndexed { i, parameter -> + parameters[i].also { parameterInfo -> + val name = createNameFromSuggested( + parameterInfo.suggestedName, + project, + VariableKind.PARAMETER, + parameterInfo.type, + context = body.sourcePsi + ) ?: return null + parameter.nameIdentifier?.replace(factory.createIdentifier(name)) + + if (needsType) { + parameter.typeElement?.replace(factory.createTypeElement(parameterInfo.type!!)) + } + else { + parameter.removeTypeElement() + } + } } - if (lambda.parameterList.parameters.any { it.typeElement == null }) - lambda.parameterList.parameters.forEach { - it.typeElement?.delete() - if (it.children.getOrNull(1) !is PsiIdentifier) - it.children.getOrNull(1)?.delete() - } - - if (lambda.parameterList.parametersCount == 1 && lambda.parameterList.parameters[0].typeElement == null) { + if (lambda.parameterList.parametersCount == 1) { + lambda.parameterList.parameters[0].removeTypeElement() lambda.parameterList.firstChild.delete() lambda.parameterList.lastChild.delete() } @@ -136,10 +290,11 @@ class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { return JavaULambdaExpression(lambda, null) } - private fun normalizeBlockForLambda(block: PsiCodeBlock): PsiElement = block - .takeIf { block.statementCount == 1 } - ?.let { block.statements[0] as? PsiReturnStatement } - ?.let { it.returnValue } ?: block + private fun normalizeBlockForLambda(block: PsiCodeBlock): PsiElement = + block + .takeIf { block.statementCount == 1 } + ?.let { block.statements[0] as? PsiReturnStatement } + ?.let { it.returnValue } ?: block override fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? { val factory = JavaPsiFacade.getElementFactory(expression.sourcePsi?.project ?: return null) @@ -175,7 +330,7 @@ class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { else -> return null } val psiBinaryExpression = factory.createExpressionFromText("a $operatorSymbol b", null) as? PsiBinaryExpression - ?: return null + ?: return null psiBinaryExpression.lOperand.replace(leftPsi) psiBinaryExpression.rOperand?.replace(rightPsi) @@ -191,10 +346,45 @@ class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin { val dummyParent = factory.createStatementFromText("a;", null) dummyParent.firstChild.replace(binarySourcePsi) ParenthesesUtils.removeParentheses(dummyParent.firstChild as PsiExpression, false) - return when(val result = dummyParent.firstChild) { + return when (val result = dummyParent.firstChild) { is PsiBinaryExpression -> JavaUBinaryExpression(result, null) is PsiPolyadicExpression -> JavaUPolyadicExpression(result, null) - else -> throw AssertionError("Unexpected type " + result.javaClass) + else -> error("Unexpected type " + result.javaClass) } } -} \ No newline at end of file +} + +private val PsiElement.branchStatement: PsiStatement? + get() = when (this) { + is PsiExpression -> JavaPsiFacade.getElementFactory(this.project).createExpresionStatement(this) + is PsiCodeBlock -> BlockUtils.createBlockStatement(this.project).also { it.codeBlock.replace(this) } + is PsiStatement -> this + else -> null + } + +private fun PsiElementFactory.createExpresionStatement(expression: PsiExpression): PsiStatement? { + val statement = createStatementFromText("x;", null) as? PsiExpressionStatement ?: return null + statement.expression.replace(expression) + return statement +} + +private fun PsiVariable.removeTypeElement() { + this.typeElement?.delete() + if (this.children.getOrNull(1) !is PsiIdentifier) { + this.children.getOrNull(1)?.delete() + } +} + +private fun createNameFromSuggested(suggestedName: String?, + project: Project, + variableKind: VariableKind, + type: PsiType? = null, + initializer: PsiExpression? = null, + context: PsiElement? = null): String? { + val codeStyleManager = JavaCodeStyleManager.getInstance(project) + val name = suggestedName ?: codeStyleManager.generateVariableName(type, initializer, variableKind) ?: return null + return codeStyleManager.suggestUniqueVariableName(name, context, false) +} + +private fun JavaCodeStyleManager.generateVariableName(type: PsiType?, initializer: PsiExpression?, kind: VariableKind) = + suggestVariableName(kind, null, initializer, type).names.firstOrNull() \ No newline at end of file diff --git a/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt b/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt index d5ef8a79ae8f..89ecca6e6975 100644 --- a/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt +++ b/uast/uast-tests/test/org/jetbrains/uast/test/java/generate/JavaUastGenerationTest.kt @@ -5,6 +5,7 @@ import com.intellij.lang.java.JavaLanguage import com.intellij.psi.* import junit.framework.TestCase import org.jetbrains.uast.* +import org.jetbrains.uast.generate.UParameterInfo import org.jetbrains.uast.generate.UastCodeGenerationPlugin import org.jetbrains.uast.generate.refreshed import org.jetbrains.uast.generate.replace @@ -111,11 +112,23 @@ class JavaUastGenerationTest : AbstractJavaUastLightTest() { val expression = psiFactory.createExpressionFromText("b", null).toUElementOfType() ?: fail("cannot create variable declaration") - val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression, true) ?: fail("cannot create variable") + val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression, true) + ?: fail("cannot create variable") TestCase.assertEquals("final double a = b;", declaration.sourcePsi?.text) } + fun `test final variable declaration with unique name`() { + val context = psiFactory.createVariableDeclarationStatement("a", PsiType.INT, null, null) + val expression = psiFactory.createExpressionFromText("b", context).toUElementOfType() + ?: fail("cannot create variable declaration") + + val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression, true) + ?: fail("cannot create variable") + + TestCase.assertEquals("final double a1 = b;", declaration.sourcePsi?.text) + } + fun `test block expression`() { val statement1 = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType() ?: fail("cannot create statement") @@ -134,28 +147,51 @@ class JavaUastGenerationTest : AbstractJavaUastLightTest() { val statement = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType() ?: fail("cannot create statement") - val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter") - - val parameter2 = psiFactory.createParameter("b", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter") - (parameter2.sourcePsi as PsiParameter).typeElement?.delete() - (parameter2.sourcePsi as PsiParameter).children[1].delete() - - val lambda = generatePlugin.createLambdaExpression(listOf(parameter1, parameter2), statement) ?: fail("cannot create lambda") + val lambda = generatePlugin.createLambdaExpression( + listOf( + UParameterInfo(PsiType.INT, "a"), + UParameterInfo(null, "b") + ), + statement + ) ?: fail("cannot create lambda") TestCase.assertEquals("(a,b)->System.out.println()", lambda.sourcePsi?.text) } + fun `test lambda expression with explicit types`() { + val statement = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType() + ?: fail("cannot create statement") + + val lambda = generatePlugin.createLambdaExpression( + listOf( + UParameterInfo(PsiType.INT, "a"), + UParameterInfo(PsiType.DOUBLE, "b") + ), + statement + ) ?: fail("cannot create lambda") + + TestCase.assertEquals("(int a,double b)->System.out.println()", lambda.sourcePsi?.text) + } + fun `test lambda expression with simplified block body`() { val block = psiFactory.createStatementFromText("{ return \"10\"; }", null).toUElementOfType() ?: fail("cannot create block") - val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType() ?: fail("cannot create parameter") - (parameter1.sourcePsi as PsiParameter).typeElement?.delete() - (parameter1.sourcePsi as PsiParameter).children[1].delete() - val lambda = generatePlugin.createLambdaExpression(listOf(parameter1), block) ?: fail("cannot create lambda") + val lambda = generatePlugin.createLambdaExpression(listOf(UParameterInfo(null, "a")), block) + ?: fail("cannot create lambda") TestCase.assertEquals("""a->"10"""", lambda.sourcePsi?.text) } + fun `test lambda expression with simplified block body with context`() { + val context = psiFactory.createVariableDeclarationStatement("a", PsiType.INT, null) + val block = psiFactory.createStatementFromText("{ return \"10\"; }", context).toUElementOfType() + ?: fail("cannot create block") + + val lambda = generatePlugin.createLambdaExpression(listOf(UParameterInfo(null, "a")), block) + ?: fail("cannot create lambda") + TestCase.assertEquals("""a1->"10"""", lambda.sourcePsi?.text) + } + fun `test function argument replacement`() { val expression = psiFactory.createExpressionFromText("f(a)", null).toUElementOfType() ?: fail("cannot create expression") @@ -177,4 +213,127 @@ class JavaUastGenerationTest : AbstractJavaUastLightTest() { TestCase.assertEquals("final int i = f(a) + 1;", variable.sourcePsi?.text) } + + fun `test method call generation with receiver`() { + val receiver = psiFactory.createExpressionFromText(""""10"""", null).toUElementOfType() + ?: fail("cannot create receiver") + val arg1 = psiFactory.createExpressionFromText("1", null).toUElementOfType() + ?: fail("cannot create arg1") + val arg2 = psiFactory.createExpressionFromText("2", null).toUElementOfType() + ?: fail("cannot create arg2") + val methodCall = generatePlugin.createCallExpression( + receiver, + "substring", + listOf(arg1, arg2), + project, + null, + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + + TestCase.assertEquals(""""10".substring(1, 2)""", methodCall.sourcePsi?.text) + } + + fun `test method call generation without receiver`() { + val arg1 = psiFactory.createExpressionFromText("1", null).toUElementOfType() + ?: fail("cannot create arg1") + val arg2 = psiFactory.createExpressionFromText("2", null).toUElementOfType() + ?: fail("cannot create arg2") + val methodCall = generatePlugin.createCallExpression( + null, + "substring", + listOf(arg1, arg2), + project, + null, + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + + TestCase.assertEquals("""substring(1, 2)""", methodCall.sourcePsi?.text) + } + + fun `test method call generation with generics restoring`() { + val arrays = psiFactory.createExpressionFromText("java.util.Arrays", null).toUElementOfType() + ?: fail("cannot create receiver") + val methodCall = generatePlugin.createCallExpression(arrays, + "asList", + listOf(), + project, + psiFactory.createTypeFromText("java.util.List", null), + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + TestCase.assertEquals("java.util.Arrays.asList()", methodCall.sourcePsi?.text) + } + + fun `test method call generation with generics restoring 2 parameters`() { + val collections = psiFactory.createExpressionFromText("java.util.Collections", null).toUElementOfType() + ?: fail("cannot create receiver") + val methodCall = generatePlugin.createCallExpression(collections, + "emptyMap", + listOf(), + project, + psiFactory.createTypeFromText( + "java.util.Map", + null + ), + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + TestCase.assertEquals("java.util.Collections.emptyMap()", methodCall.sourcePsi?.text) + } + + fun `test method call generation with generics restoring 1 parameter with 1 existing`() { + val newClass = myFixture.addClass(""" + class A { + public static java.util.Map kek(T2 a) { + return null; + } + } + """.trimIndent()) + val a = psiFactory.createExpressionFromText("A", newClass).toUElementOfType() + ?: fail("cannot create a receiver") + val param = psiFactory.createExpressionFromText("\"a\"", null).toUElementOfType() + ?: fail("cannot create a parameter") + val methodCall = generatePlugin.createCallExpression(a, + "kek", + listOf(param), + project, + psiFactory.createTypeFromText( + "java.util.Map", + null + ), + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + + TestCase.assertEquals("A.kek(\"a\")", methodCall.sourcePsi?.text) + } + + fun `test method call generation with generics restoring 1 parameter with 1 unused `() { + val newClass = myFixture.addClass(""" + class A { + public static java.util.Map kek(T1 a) { + return null; + } + } + """.trimIndent()) + val a = psiFactory.createExpressionFromText("A", newClass).toUElementOfType() + ?: fail("cannot create a receiver") + val param = psiFactory.createExpressionFromText("\"a\"", null).toUElementOfType() + ?: fail("cannot create a parameter") + val methodCall = generatePlugin.createCallExpression(a, + "kek", + listOf(param), + project, + psiFactory.createTypeFromText( + "java.util.Map", + null + ), + UastCallKind.METHOD_CALL) ?: fail("cannot create call") + + TestCase.assertEquals("A.kek(\"a\")", methodCall.sourcePsi?.text) + } + + fun `test create if`() { + val condition = psiFactory.createExpressionFromText("true", null).toUElementOfType() + ?: fail("cannot create condition") + val thenBranch = psiFactory.createCodeBlockFromText("{a(b);}", null).toUElementOfType() + ?: fail("cannot create then branch") + val elseBranch = psiFactory.createExpressionFromText("c++", null).toUElementOfType() + ?: fail("cannot create else branch") + val ifExpression = generatePlugin.createIfExpression(condition, thenBranch, elseBranch) + ?: fail("cannot create if expression") + TestCase.assertEquals("if (true) {a(b);} else c++;", ifExpression.sourcePsi?.text) + } } \ No newline at end of file