diff --git a/uast/uast-common/src/org/jetbrains/uast/UastLanguagePlugin.kt b/uast/uast-common/src/org/jetbrains/uast/UastLanguagePlugin.kt index abc0576736a9..ac82337d6261 100644 --- a/uast/uast-common/src/org/jetbrains/uast/UastLanguagePlugin.kt +++ b/uast/uast-common/src/org/jetbrains/uast/UastLanguagePlugin.kt @@ -114,6 +114,11 @@ interface UastLanguagePlugin { ?.takeIf { result -> requiredTypes.any { it.isAssignableFrom(result.javaClass) } } } as? T + + @JvmDefault + fun convertToAlternatives(element: PsiElement, requiredTypes: List>): Sequence = + sequenceOf(convertElementWithParent(element, requiredTypes)).filterNotNull() + } inline fun UastLanguagePlugin.convertOpt(element: PsiElement?, parent: UElement?): T? { diff --git a/uast/uast-java/src/org/jetbrains/uast/java/JavaUastLanguagePlugin.kt b/uast/uast-java/src/org/jetbrains/uast/java/JavaUastLanguagePlugin.kt index c896b12e394b..fc4ada3697ee 100644 --- a/uast/uast-java/src/org/jetbrains/uast/java/JavaUastLanguagePlugin.kt +++ b/uast/uast-java/src/org/jetbrains/uast/java/JavaUastLanguagePlugin.kt @@ -100,7 +100,7 @@ class JavaUastLanguagePlugin : UastLanguagePlugin { @Suppress("UNCHECKED_CAST") fun convertElement(element: PsiElement, parent: UElement?, requiredTypes: List>): T? { - val nonEmptyRequiredTypes = requiredTypes.takeIf { it.isNotEmpty() } ?: DEFAULT_TYPES_LIST + val nonEmptyRequiredTypes = requiredTypes.nonEmptyOr(DEFAULT_TYPES_LIST) if (checkCanConvert && !canConvert(element.javaClass, nonEmptyRequiredTypes)) return null return (convertDeclaration(element, parent, nonEmptyRequiredTypes) @@ -111,6 +111,14 @@ class JavaUastLanguagePlugin : UastLanguagePlugin { return convertElement(element, null, requiredTypes) } + override fun convertToAlternatives(element: PsiElement, requiredTypes: List>) = when (element) { + is `PsiMethodCallExpression` -> + JavaConverter.psiMethodCallConversionAlternatives(element, + null, + requiredTypes.nonEmptyOr(DEFAULT_EXPRESSION_TYPES_LIST)) as Sequence + else -> sequenceOf(convertElementWithParent(element, requiredTypes.nonEmptyOr(DEFAULT_TYPES_LIST)) as? T).filterNotNull() + } + private fun convertDeclaration(element: PsiElement, givenParent: UElement?, requiredType: List>): UElement? { @@ -143,7 +151,7 @@ class JavaUastLanguagePlugin : UastLanguagePlugin { } } -internal inline fun Class?.el(f: () -> UElement?): UElement? { +internal inline fun Class<*>?.el(f: () -> UElement?): UElement? { return if (this == null || isAssignableFrom(ActualT::class.java)) f() else null } @@ -237,25 +245,7 @@ internal object JavaConverter { else expr(build(::JavaConstructorUCallExpression)) } - is PsiMethodCallExpression -> { - if (el.methodExpression.qualifierExpression != null) { - if (requiredType.isAssignableFrom(UQualifiedReferenceExpression::class.java) || - requiredType.isAssignableFrom(UCallExpression::class.java)) { - val expr = JavaUCompositeQualifiedExpression(el, givenParent).apply { - receiverInitializer = { convertOrEmpty(el.methodExpression.qualifierExpression!!, this) } - selector = JavaUCallExpression(el, this) - } - if (!requiredType.isAssignableFrom(UQualifiedReferenceExpression::class.java)) - expr.selector - else - expr - } - else - null - } - else - expr(build(::JavaUCallExpression)) - } + is PsiMethodCallExpression -> psiMethodCallConversionAlternatives(el, givenParent, requiredType).firstOrNull() is PsiArrayInitializerExpression -> expr(build(::JavaArrayInitializerUCallExpression)) is PsiBinaryExpression -> expr(build(::JavaUBinaryExpression)) // Should go after PsiBinaryExpression since it implements PsiPolyadicExpression @@ -279,6 +269,28 @@ internal object JavaConverter { } } + internal fun psiMethodCallConversionAlternatives(element: PsiMethodCallExpression, + givenParent: UElement?, + requiredTypes: List>): Sequence { + if (element.methodExpression.qualifierExpression == null) { + return sequenceOf(requiredTypes.expr { JavaUCallExpression(element, givenParent) }).filterNotNull() + } + + if (!requiredTypes.isAssignableFrom(UQualifiedReferenceExpression::class.java) && + !requiredTypes.isAssignableFrom(UCallExpression::class.java)) return emptySequence() + + + val expr = JavaUCompositeQualifiedExpression(element, givenParent).apply { + receiverInitializer = { + convertOrEmpty(element.methodExpression.qualifierExpression!!, this@apply) + } + selector = JavaUCallExpression(element, this@apply) + } + + val results = sequenceOf(expr, expr.selector) + return requiredTypes.asSequence().flatMap { required -> results.filter { required.isInstance(it) } }.distinct() + } + internal fun convertStatement(el: PsiStatement, givenParent: UElement?, requiredType: List> = DEFAULT_EXPRESSION_TYPES_LIST): UExpression? { @@ -363,4 +375,6 @@ internal object JavaConverter { private fun expressionTypes(requiredType: Class?) = requiredType?.let { listOf(it) } ?: DEFAULT_EXPRESSION_TYPES_LIST -private fun elementTypes(requiredType: Class?) = requiredType?.let { listOf(it) } ?: DEFAULT_TYPES_LIST \ No newline at end of file +private fun elementTypes(requiredType: Class?) = requiredType?.let { listOf(it) } ?: DEFAULT_TYPES_LIST + +private fun List>.nonEmptyOr(default: List>) = takeIf { it.isNotEmpty() } ?: default \ No newline at end of file diff --git a/uast/uast-tests/test/org/jetbrains/uast/test/java/JavaUastApiTest.kt b/uast/uast-tests/test/org/jetbrains/uast/test/java/JavaUastApiTest.kt index d97e6320bad8..46fea364a8e0 100644 --- a/uast/uast-tests/test/org/jetbrains/uast/test/java/JavaUastApiTest.kt +++ b/uast/uast-tests/test/org/jetbrains/uast/test/java/JavaUastApiTest.kt @@ -75,6 +75,36 @@ class JavaUastApiTest : AbstractJavaUastTest() { } } + + @Test + fun testCallExpressionAlternatives() { + doTest("Simple/CallExpression.java") { name, file -> + val index = file.psi.text.indexOf("format") + val callExpression = PsiTreeUtil.getParentOfType(file.psi.findElementAt(index), PsiCallExpression::class.java)!! + + val javaUastLanguagePlugin = UastLanguagePlugin.byLanguage(callExpression.language)!! + + javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UCallExpression::class.java)).let { + assertEquals("format(\"q\")", it.joinToString(transform = UExpression::asRenderString)) + } + + javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UQualifiedReferenceExpression::class.java, + UCallExpression::class.java)).let { + assertEquals("String.format(\"q\"), format(\"q\")", it.joinToString(transform = UExpression::asRenderString)) + } + + javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UCallExpression::class.java, + UQualifiedReferenceExpression::class.java)).let { + assertEquals("format(\"q\"), String.format(\"q\")", it.joinToString(transform = UExpression::asRenderString)) + } + + javaUastLanguagePlugin.convertToAlternatives(callExpression, listOf(UExpression::class.java)).let { + assertEquals("String.format(\"q\"), format(\"q\")", it.joinToString(transform = UExpression::asRenderString)) + } + + } + } + @Test fun testCallExpressionArguments() { doTest("Simple/CallExpression.java") { name, file ->