KT-11425: convert a.compareTo(b) to binary comparison if possible

(cherry picked from commit 7219904)
This commit is contained in:
Mikhail Glukhikh
2016-07-28 11:36:56 +03:00
committed by Mikhail Glukhikh
parent f309021c9f
commit fc13b25ca4
8 changed files with 98 additions and 13 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.intentions.conventionNameCalls
import com.intellij.codeInsight.intention.HighPriorityAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.tree.IElementType
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.idea.intentions.*
import org.jetbrains.kotlin.lexer.KtSingleValueToken
@@ -36,6 +37,17 @@ class ReplaceCallWithBinaryOperatorIntention : SelfTargetingRangeIntention<KtDot
KtDotQualifiedExpression::class.java,
"Replace call with binary operator"
), HighPriorityAction {
private fun IElementType.inverted(): KtSingleValueToken? = when (this) {
KtTokens.LT -> KtTokens.GT
KtTokens.GT -> KtTokens.LT
KtTokens.GTEQ -> KtTokens.LTEQ
KtTokens.LTEQ -> KtTokens.GTEQ
else -> null
}
override fun applicabilityRange(element: KtDotQualifiedExpression): TextRange? {
val calleeExpression = element.callExpression?.calleeExpression as? KtSimpleNameExpression ?: return null
val operation = operation(calleeExpression) ?: return null
@@ -58,15 +70,24 @@ class ReplaceCallWithBinaryOperatorIntention : SelfTargetingRangeIntention<KtDot
val argument = callExpression.valueArguments.single().getArgumentExpression() ?: return
val receiver = element.receiverExpression
if (operation == KtTokens.EXCLEQ) {
val prefixExpression = element.getWrappingPrefixExpressionIfAny() ?: return
val newExpression = KtPsiFactory(element).createExpressionByPattern("$0 != $1", receiver, argument)
prefixExpression.replace(newExpression)
}
else {
val newExpression = KtPsiFactory(element).createExpressionByPattern("$0 ${operation.value} $1", receiver, argument)
element.replace(newExpression)
val factory = KtPsiFactory(element)
when (operation) {
KtTokens.EXCLEQ -> {
val prefixExpression = element.getWrappingPrefixExpressionIfAny() ?: return
val newExpression = factory.createExpressionByPattern("$0 != $1", receiver, argument)
prefixExpression.replace(newExpression)
}
in OperatorConventions.COMPARISON_OPERATIONS -> {
val binaryParent = element.parent as? KtBinaryExpression ?: return
val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument)
binaryParent.replace(newExpression)
}
else -> {
val newExpression = factory.createExpressionByPattern("$0 ${operation.value} $1", receiver, argument)
element.replace(newExpression)
}
}
}
private fun PsiElement.getWrappingPrefixExpressionIfAny() =
@@ -74,11 +95,31 @@ class ReplaceCallWithBinaryOperatorIntention : SelfTargetingRangeIntention<KtDot
private fun operation(calleeExpression: KtSimpleNameExpression): KtSingleValueToken? {
val identifier = calleeExpression.getReferencedNameAsName()
if (identifier == OperatorNameConventions.EQUALS) {
val prefixExpression = calleeExpression.parent?.parent?.getWrappingPrefixExpressionIfAny()
return if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
else KtTokens.EQEQ
return when (identifier) {
OperatorNameConventions.EQUALS -> {
val prefixExpression = calleeExpression.parent?.parent?.getWrappingPrefixExpressionIfAny()
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
else KtTokens.EQEQ
}
OperatorNameConventions.COMPARE_TO -> {
// callee -> call -> DotQualified -> Binary
val dotQualified = calleeExpression.parent?.parent
val binaryParent = dotQualified?.parent as? KtBinaryExpression ?: return null
val notZero = when {
binaryParent.right?.text == "0" -> binaryParent.left
binaryParent.left?.text == "0" -> binaryParent.right
else -> return null
}
if (notZero != dotQualified) return null
val token = binaryParent.operationToken as? KtSingleValueToken ?: return null
if (token in OperatorConventions.COMPARISON_OPERATIONS) {
if (notZero == binaryParent.left) token else token.inverted()
}
else {
null
}
}
else -> OperatorConventions.BINARY_OPERATION_NAMES.inverse()[identifier]
}
return OperatorConventions.BINARY_OPERATION_NAMES.inverse()[identifier]
}
}
@@ -0,0 +1,3 @@
// IS_APPLICABLE: false
val x = 2.compareTo<caret>(2) == 0
@@ -0,0 +1,3 @@
// INTENTION_TEXT: Replace with '>' operator
val x = 3.compareTo<caret>(2) > 0
@@ -0,0 +1,3 @@
// INTENTION_TEXT: Replace with '>' operator
val x = 3 > 2
@@ -0,0 +1,3 @@
// INTENTION_TEXT: Replace with '<=' operator
val x = 0 >= 4.compareTo<caret>(5)
@@ -0,0 +1,3 @@
// INTENTION_TEXT: Replace with '<=' operator
val x = 4 <= 5
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
// ERROR: Infix call corresponds to a dot-qualified call 'nullable?.compareTo(1).compareTo(0)' which is not allowed on a nullable receiver 'nullable?.compareTo(1)'. Use '?.'-qualified call instead
val nullable: Int? = null
val x = nullable?.compareTo<caret>(1) >= 0
@@ -2744,6 +2744,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("equalsCompareTo.kt")
public void testEqualsCompareTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/equalsCompareTo.kt");
doTest(fileName);
}
@TestMetadata("extensionFunction.kt")
public void testExtensionFunction() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/extensionFunction.kt");
@@ -2756,6 +2762,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("greater.kt")
public void testGreater() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/greater.kt");
doTest(fileName);
}
@TestMetadata("lessEquals.kt")
public void testLessEquals() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/lessEquals.kt");
doTest(fileName);
}
@TestMetadata("minusSanityTest.kt")
public void testMinusSanityTest() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/minusSanityTest.kt");
@@ -2816,6 +2834,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
doTest(fileName);
}
@TestMetadata("safeCompareTo.kt")
public void testSafeCompareTo() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/safeCompareTo.kt");
doTest(fileName);
}
@TestMetadata("super.kt")
public void testSuper() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/conventionNameCalls/replaceCallWithBinaryOperator/super.kt");