[uast] small part of implementation of code generation/modification on UAST. reviewed in IDEA-CR-50669

GitOrigin-RevId: 89328f198c55576137f831c1963bf359cdae9fa9
This commit is contained in:
aleksandr.izmaylov
2019-07-31 19:04:01 +03:00
committed by intellij-monorepo-bot
parent 63597e52a1
commit fb0bfeda5a
5 changed files with 458 additions and 4 deletions
@@ -51,6 +51,8 @@
<extensionPoint qualifiedName="org.jetbrains.uast.uastLanguagePlugin" interface="org.jetbrains.uast.UastLanguagePlugin"/>
<extensionPoint qualifiedName="org.jetbrains.uast.analysis.uastAnalysisPlugin"
interface="org.jetbrains.uast.analysis.UastAnalysisPlugin"/>
<extensionPoint qualifiedName="org.jetbrains.uast.generate.uastCodeGenerationPlugin"
interface="org.jetbrains.uast.generate.UastCodeGenerationPlugin"/>
<extensionPoint qualifiedName="com.intellij.jvm.declarationSearcher" beanClass="com.intellij.lang.LanguageExtensionPoint">
<with attribute="implementationClass" implements="com.intellij.lang.jvm.source.JvmDeclarationSearcher"/>
</extensionPoint>
@@ -126,10 +128,8 @@
<extensions defaultExtensionNs="org.jetbrains.uast">
<uastLanguagePlugin implementation="org.jetbrains.uast.java.JavaUastLanguagePlugin"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.uast.analysis">
<uastAnalysisPlugin implementation="org.jetbrains.uast.java.analysis.JavaUastAnalysisPlugin"/>
<analysis.uastAnalysisPlugin implementation="org.jetbrains.uast.java.analysis.JavaUastAnalysisPlugin"/>
<generate.uastCodeGenerationPlugin implementation="org.jetbrains.uast.java.generate.JavaUastCodeGenerationPlugin"/>
</extensions>
<extensions defaultExtensionNs="com.intellij.jvm">
@@ -0,0 +1,63 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.uast.generate
import com.intellij.lang.Language
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiType
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.uast.*
@ApiStatus.Experimental
interface UastCodeGenerationPlugin {
companion object {
val extensionPointName = ExtensionPointName<UastCodeGenerationPlugin>("org.jetbrains.uast.generate.uastCodeGenerationPlugin")
private val extensions by lazy { extensionPointName.extensionList }
@JvmStatic
fun byLanguage(language: Language) = extensions.firstOrNull { it.language == language }
}
val language: Language
fun createBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UBinaryExpression?
/**
* Create binary expression, and possibly remove unnecessary parenthesis, so it could become [UPolyadicExpression], e.g
* [createFlatBinaryExpression](1 + 2, 2, +) could produce 1 + 2 + 2, which is polyadic expression
*/
@JvmDefault
fun createFlatBinaryExpression(leftOperand: UExpression, rightOperand: UExpression, operator: UastBinaryOperator): UPolyadicExpression? =
createBinaryExpression(leftOperand, rightOperand, operator)
fun createSimpleReference(name: String, project: Project): USimpleNameReferenceExpression?
fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression?
fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression?
fun createReturnExpresion(expression: UExpression, inLambda: Boolean = false): UReturnExpression?
fun createLocalVariable(name: String?, type: PsiType?, initializer: UExpression, immutable: Boolean = false): ULocalVariable?
fun createBlockExpression(expressions: List<UExpression>, project: Project): UBlockExpression?
fun createLambdaExpression(parameters: List<UParameter>, body: UExpression): ULambdaExpression?
fun createDeclarationExpression(declarations: List<UDeclaration>, project: Project): UDeclarationsExpression?
fun <T: UElement> replace(oldElement: UElement, newElement: T, elementType: Class<T>): T?
}
@ApiStatus.Experimental
inline fun <reified T: UElement> UElement.replace(newElement: T): T? =
this.getLanguagePlugin()
.let { UastCodeGenerationPlugin.byLanguage(it.language) }
?.replace(this, newElement, T::class.java)
@ApiStatus.Experimental
inline fun <reified T : UElement> T.refreshed() = sourcePsi?.toUElementOfType<T>()
val UElement.generationPlugin: UastCodeGenerationPlugin?
@ApiStatus.Experimental
get() = getLanguagePlugin().let { UastCodeGenerationPlugin.byLanguage(it.language) }
@@ -0,0 +1,200 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.uast.java.generate
import com.intellij.codeInsight.BlockUtils
import com.intellij.lang.Language
import com.intellij.lang.java.JavaLanguage
import com.intellij.lang.jvm.JvmModifier
import com.intellij.openapi.project.Project
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.PsiUtil
import com.siyeh.ig.psiutils.ParenthesesUtils
import org.jetbrains.uast.*
import org.jetbrains.uast.generate.UastCodeGenerationPlugin
import org.jetbrains.uast.java.*
import java.lang.AssertionError
class JavaUastCodeGenerationPlugin : UastCodeGenerationPlugin {
override fun <T: UElement> replace(oldElement: UElement, newElement: T, elementType: Class<T>): T? =
newElement.sourcePsi?.let { oldElement.sourcePsi?.replace(it) }?.toUElementOfExpectedTypes(elementType)
override fun createDeclarationExpression(declarations: List<UDeclaration>, 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
(returnStatement as CompositeElement).addChild(expression.sourcePsi!!.node, returnStatement.lastChild.node)
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)
}
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)
}
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<UExpression>, project: Project): UBlockExpression? {
val factory = JavaPsiFacade.getElementFactory(project)
val blockStatement = BlockUtils.createBlockStatement(project)
for (expression in expressions) {
if (expression is JavaUDeclarationsExpression) {
for (declaration in expression.declarations) {
if (declaration.sourcePsi is PsiLocalVariable) {
blockStatement.codeBlock.add(declaration.sourcePsi?.parent as? PsiDeclarationStatement ?: return null)
}
}
continue
}
expression.sourcePsi?.let { psi ->
psi as? PsiStatement ?: (psi as? PsiExpression)?.let { factory.createExpresionStatement(it) }
}?.let { blockStatement.codeBlock.add(it) } ?: return null
}
return JavaUBlockExpression(blockStatement, null)
}
override fun createLambdaExpression(parameters: List<UParameter>, body: UExpression): ULambdaExpression? {
val factory = JavaPsiFacade.getElementFactory(body.sourcePsi?.project ?: return null)
val lambda = factory.createExpressionFromText(
buildString {
parameters.joinTo(this, separator = ",", prefix = "(", postfix = ")") { "x" }
append("->")
append("{}")
},
null
) as? PsiLambdaExpression ?: return null
lambda.parameterList.parameters.forEachIndexed { i, p ->
p.replace(parameters[i].sourcePsi ?: return null)
}
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) {
lambda.parameterList.firstChild.delete()
lambda.parameterList.lastChild.delete()
}
val normalizedBody = when (val bodyPsi = body.sourcePsi) {
is PsiExpression -> bodyPsi
is PsiCodeBlock -> normalizeBlockForLambda(bodyPsi)
is PsiBlockStatement -> normalizeBlockForLambda(bodyPsi.codeBlock)
else -> return null
}
lambda.body?.replace(normalizedBody) ?: return null
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
override fun createParenthesizedExpression(expression: UExpression): UParenthesizedExpression? {
val factory = JavaPsiFacade.getElementFactory(expression.sourcePsi?.project ?: return null)
val parenthesizedExpression = factory.createExpressionFromText("()", null) as? PsiParenthesizedExpression ?: return null
parenthesizedExpression.children.getOrNull(1)?.replace(expression.sourcePsi ?: return null) ?: return null
return JavaUParenthesizedExpression(parenthesizedExpression, null)
}
override fun createSimpleReference(name: String, project: Project): USimpleNameReferenceExpression? {
val factory = JavaPsiFacade.getElementFactory(project)
val reference = factory.createExpressionFromText(name, null)
return JavaUSimpleNameReferenceExpression(reference, name, null)
}
override fun createSimpleReference(variable: UVariable): USimpleNameReferenceExpression? {
return createSimpleReference(variable.name ?: return null, variable.sourcePsi?.project ?: return null)
}
override val language: Language
get() = JavaLanguage.INSTANCE
override fun createBinaryExpression(leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator): UBinaryExpression? {
val project = leftOperand.sourcePsi?.project ?: return null
val leftPsi = leftOperand.sourcePsi ?: return null
val rightPsi = rightOperand.sourcePsi ?: return null
val factory = JavaPsiFacade.getElementFactory(project)
val operatorSymbol = when (operator) {
UastBinaryOperator.LOGICAL_AND -> "&&"
else -> return null
}
val psiBinaryExpression = factory.createExpressionFromText("a $operatorSymbol b", null) as? PsiBinaryExpression
?: return null
psiBinaryExpression.lOperand.replace(leftPsi)
psiBinaryExpression.rOperand?.replace(rightPsi)
return JavaUBinaryExpression(psiBinaryExpression, null)
}
override fun createFlatBinaryExpression(leftOperand: UExpression,
rightOperand: UExpression,
operator: UastBinaryOperator): UPolyadicExpression? {
val binaryExpression = createBinaryExpression(leftOperand, rightOperand, operator) ?: return null
val binarySourcePsi = binaryExpression.sourcePsi as? PsiBinaryExpression ?: return null
val factory = JavaPsiFacade.getElementFactory(binarySourcePsi.project)
val dummyParent = factory.createStatementFromText("a;", null)
dummyParent.firstChild.replace(binarySourcePsi)
ParenthesesUtils.removeParentheses(dummyParent.firstChild as PsiExpression, false)
return when(val result = dummyParent.firstChild) {
is PsiBinaryExpression -> JavaUBinaryExpression(result, null)
is PsiPolyadicExpression -> JavaUPolyadicExpression(result, null)
else -> throw AssertionError("Unexpected type " + result.javaClass)
}
}
}
@@ -0,0 +1,11 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.uast.test.java
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
import junit.framework.TestCase
abstract class AbstractJavaUastLightTest : LightJavaCodeInsightFixtureTestCase() {
protected fun fail(message: String): Nothing {
throw AssertionError(message)
}
}
@@ -0,0 +1,180 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.uast.test.java.generate
import com.intellij.lang.java.JavaLanguage
import com.intellij.psi.*
import junit.framework.TestCase
import org.jetbrains.uast.*
import org.jetbrains.uast.generate.UastCodeGenerationPlugin
import org.jetbrains.uast.generate.refreshed
import org.jetbrains.uast.generate.replace
import org.jetbrains.uast.test.java.AbstractJavaUastLightTest
class JavaUastGenerationTest : AbstractJavaUastLightTest() {
private val psiFactory: PsiElementFactory
get() = JavaPsiFacade.getElementFactory(myFixture.project)
private val generatePlugin: UastCodeGenerationPlugin
get() = UastCodeGenerationPlugin.byLanguage(JavaLanguage.INSTANCE)!!
fun `test logical and operation with simple operands`() {
val left = psiFactory.createExpressionFromText("true", null).toUElementOfType<UExpression>()
?: fail("Cannot create left UExpression")
val right = psiFactory.createExpressionFromText("false", null).toUElementOfType<UExpression>()
?: fail("Cannot create right UExpression")
val expression = generatePlugin.createBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
?: fail("Cannot create expression")
TestCase.assertEquals("true && false", expression.sourcePsi?.text)
}
fun `test logical and operation with simple operands with parenthesis`() {
val left = psiFactory.createExpressionFromText("(true)", null).toUElementOfType<UExpression>()
?: fail("Cannot create left UExpression")
val right = psiFactory.createExpressionFromText("(false)", null).toUElementOfType<UExpression>()
?: fail("Cannot create right UExpression")
val expression = generatePlugin.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
?: fail("Cannot create expression")
TestCase.assertTrue(expression.sourcePsi is PsiBinaryExpression)
TestCase.assertEquals("true && false", expression.sourcePsi?.text)
}
fun `test logical and operation with simple operands with parenthesis polyadic`() {
val left = psiFactory.createExpressionFromText("(true && false)", null).toUElementOfType<UExpression>()
?: fail("Cannot create left UExpression")
val right = psiFactory.createExpressionFromText("(false)", null).toUElementOfType<UExpression>()
?: fail("Cannot create right UExpression")
val expression = generatePlugin.createFlatBinaryExpression(left, right, UastBinaryOperator.LOGICAL_AND)
?: fail("Cannot create expression")
TestCase.assertTrue(expression.sourcePsi is PsiPolyadicExpression)
TestCase.assertEquals("true && false && false", expression.sourcePsi?.text)
}
fun `test simple reference creating from variable`() {
val variable = psiFactory.createVariableDeclarationStatement(
"a",
PsiType.INT
, null
).declaredElements.getOrNull(0)?.toUElementOfType<UVariable>() ?: fail("cannot create variable")
val reference = generatePlugin.createSimpleReference(variable) ?: fail("cannot create reference")
TestCase.assertEquals("a", reference.identifier)
}
fun `test simple reference by name`() {
val reference = generatePlugin.createSimpleReference("a", myFixture.project) ?: fail("cannot create reference")
TestCase.assertEquals("a", reference.identifier)
}
fun `test parenthesised expression`() {
val expression = psiFactory.createExpressionFromText("a + b", null).toUElementOfType<UExpression>()
?: fail("cannot create expression")
val parenthesizedExpression = generatePlugin.createParenthesizedExpression(expression)
?: fail("cannot create parenthesized expression")
TestCase.assertEquals("(a + b)", parenthesizedExpression.sourcePsi?.text)
}
fun `test return expression`() {
val expression = psiFactory.createExpressionFromText("a + b", null).toUElementOfType<UExpression>()
?: fail("Cannot find plugin")
val returnExpression = generatePlugin.createReturnExpresion(expression) ?: fail("cannot create return expression")
TestCase.assertEquals("return a + b;", returnExpression.sourcePsi?.text)
}
fun `test variable declaration without type`() {
val expression = psiFactory.createExpressionFromText("1 + 2", null).toUElementOfType<UExpression>()
?: fail("cannot create variable declaration")
val declaration = generatePlugin.createLocalVariable("a", null, expression) ?: fail("cannot create variable")
TestCase.assertEquals("int a = 1 + 2;", declaration.sourcePsi?.text)
}
fun `test variable declaration with type`() {
val expression = psiFactory.createExpressionFromText("b", null).toUElementOfType<UExpression>()
?: fail("cannot create variable declaration")
val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression) ?: fail("cannot create variable")
TestCase.assertEquals("double a = b;", declaration.sourcePsi?.text)
}
fun `test final variable declaration`() {
val expression = psiFactory.createExpressionFromText("b", null).toUElementOfType<UExpression>()
?: fail("cannot create variable declaration")
val declaration = generatePlugin.createLocalVariable("a", PsiType.DOUBLE, expression, true) ?: fail("cannot create variable")
TestCase.assertEquals("final double a = b;", declaration.sourcePsi?.text)
}
fun `test block expression`() {
val statement1 = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType<UExpression>()
?: fail("cannot create statement")
val statement2 = psiFactory.createStatementFromText("System.out.println(2);", null).toUElementOfType<UExpression>()
?: fail("cannot create statement")
val block = generatePlugin.createBlockExpression(listOf(statement1, statement2), myFixture.project) ?: fail("cannot create block")
TestCase.assertEquals("{" +
"System.out.println();" +
"System.out.println(2);" +
"}", block.sourcePsi?.text)
}
fun `test lambda expression`() {
val statement = psiFactory.createStatementFromText("System.out.println();", null).toUElementOfType<UExpression>()
?: fail("cannot create statement")
val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType<UParameter>() ?: fail("cannot create parameter")
val parameter2 = psiFactory.createParameter("b", PsiType.INT).toUElementOfType<UParameter>() ?: 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")
TestCase.assertEquals("(a,b)->System.out.println()", lambda.sourcePsi?.text)
}
fun `test lambda expression with simplified block body`() {
val block = psiFactory.createStatementFromText("{ return \"10\"; }", null).toUElementOfType<UBlockExpression>()
?: fail("cannot create block")
val parameter1 = psiFactory.createParameter("a", PsiType.INT).toUElementOfType<UParameter>() ?: 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")
TestCase.assertEquals("""a->"10"""", lambda.sourcePsi?.text)
}
fun `test function argument replacement`() {
val expression = psiFactory.createExpressionFromText("f(a)", null).toUElementOfType<UCallExpression>()
?: fail("cannot create expression")
val newArgument = psiFactory.createExpressionFromText("b", null).toUElementOfType<USimpleNameReferenceExpression>()
?: fail("cannot create reference")
TestCase.assertNotNull(expression.valueArguments[0].replace(newArgument))
val updated = expression.refreshed() ?: fail("cannot update expression")
TestCase.assertEquals("f", updated.methodName)
TestCase.assertTrue(updated.valueArguments[0] is USimpleNameReferenceExpression)
TestCase.assertEquals("b", (updated.valueArguments[0] as USimpleNameReferenceExpression).identifier)
}
fun `test suggested name`() {
val expression = psiFactory.createExpressionFromText("f(a) + 1", null).toUElementOfType<UExpression>()
?: fail("cannot create expression")
val variable = generatePlugin.createLocalVariable(null, PsiType.INT, expression, true)
?: fail("cannot create variable")
TestCase.assertEquals("final int i = f(a) + 1;", variable.sourcePsi?.text)
}
}