diff --git a/idea/src/org/jetbrains/jet/plugin/intentions/SwapBinaryExpression.kt b/idea/src/org/jetbrains/jet/plugin/intentions/SwapBinaryExpression.kt index 30770fa5550..0946126876e 100644 --- a/idea/src/org/jetbrains/jet/plugin/intentions/SwapBinaryExpression.kt +++ b/idea/src/org/jetbrains/jet/plugin/intentions/SwapBinaryExpression.kt @@ -20,60 +20,37 @@ import com.intellij.openapi.editor.Editor import org.jetbrains.jet.lang.psi.JetBinaryExpression import org.jetbrains.jet.lang.psi.JetPsiFactory import org.jetbrains.jet.lang.psi.JetExpression -import org.jetbrains.jet.plugin.intentions.SwapBinaryExpression.Position.* import org.jetbrains.jet.plugin.util.JetPsiPrecedences +import org.jetbrains.jet.lexer.JetTokens.* +import org.jetbrains.jet.lang.types.expressions.OperatorConventions public class SwapBinaryExpression : JetSelfTargetingIntention( "swap.binary.expression", javaClass() ) { - enum class Position { - FIRST_LEFT - FIRST_RIGHT - NEXT_LEFT - NEXT_RIGHT - } + class object { + val SUPPORTED_OPERATIONS = setOf(PLUS, MUL, OROR, ANDAND, EQEQ, EXCLEQ, EQEQEQ, EXCLEQEQEQ, GT, LT, GTEQ, LTEQ) - fun getInnermostOperand(element: JetBinaryExpression, position: Position): JetExpression? { - val left = element.getLeft() - val right = element.getRight() - if (left == null || right == null) { - return null - } - - val parentPrecedence = JetPsiPrecedences.getPrecedence(element) - val leftPrecedence = JetPsiPrecedences.getPrecedence(left) - val rightPrecedence = JetPsiPrecedences.getPrecedence(right) - return when (position) { - FIRST_LEFT -> if (leftPrecedence < parentPrecedence) left - else if (left is JetBinaryExpression) getInnermostOperand(left, NEXT_RIGHT) - else left as JetExpression - FIRST_RIGHT -> if (rightPrecedence < parentPrecedence) right - else if (right is JetBinaryExpression) getInnermostOperand(right, NEXT_LEFT) - else right as JetExpression - NEXT_LEFT -> if (leftPrecedence < parentPrecedence || left !is JetBinaryExpression) left - else getInnermostOperand(left, NEXT_LEFT) - NEXT_RIGHT -> if (rightPrecedence < parentPrecedence || right !is JetBinaryExpression) right - else getInnermostOperand(right, NEXT_RIGHT) - } + val SUPPORTED_OPERATION_NAMES = SUPPORTED_OPERATIONS.map { OperatorConventions.BINARY_OPERATION_NAMES[it]?.asString() }.toSet().filterNotNull() + + setOf("xor", "or", "and", "equals", "identityEquals") } override fun isApplicableTo(element: JetBinaryExpression): Boolean { - if (getInnermostOperand(element, FIRST_LEFT) == null || getInnermostOperand(element, FIRST_RIGHT) == null) { + if (leftSubject(element) == null || rightSubject(element) == null) { return false } - val operatorTextLiteral = element.getOperationReference().getText() - val approvedOperators = setOf("+", "plus", "*", "times", "||", "or", "and", - "&&", "==", "!=", "xor", ">", "<", ">=", "<=", "equals") - - if (approvedOperators.contains(operatorTextLiteral)) { - setText("Flip '$operatorTextLiteral'") + val operationToken = element.getOperationToken() + val operationTokenText = element.getOperationReference().getText() + if (operationToken in SUPPORTED_OPERATIONS + || operationToken == IDENTIFIER && operationTokenText in SUPPORTED_OPERATION_NAMES) { + setText("Flip '$operationTokenText'") return true } return false } override fun applyTo(element: JetBinaryExpression, editor: Editor) { + // Have to use text here to preserve names like "plus" val operator = element.getOperationReference().getText()!! val convertedOperator = when (operator) { ">" -> "<" @@ -82,12 +59,30 @@ public class SwapBinaryExpression : JetSelfTargetingIntention=" -> "<=" else -> operator } - val left = getInnermostOperand(element, FIRST_LEFT)!! - val right = getInnermostOperand(element, FIRST_RIGHT)!! + val left = leftSubject(element)!! + val right = rightSubject(element)!! val newRight = JetPsiFactory.createExpression(element.getProject(), left.getText()) val newLeft = JetPsiFactory.createExpression(element.getProject(), right.getText()) left.replace(newLeft) right.replace(newRight) element.replace(JetPsiFactory.createBinaryExpression(element.getProject(), element.getLeft(), convertedOperator, element.getRight())) } + + private fun leftSubject(element: JetBinaryExpression): JetExpression? { + return firstDescendantOfTighterPrecedence(element.getLeft(), JetPsiPrecedences.getPrecedence(element), JetBinaryExpression::getRight) + } + + private fun rightSubject(element: JetBinaryExpression): JetExpression? { + return firstDescendantOfTighterPrecedence(element.getRight(), JetPsiPrecedences.getPrecedence(element), JetBinaryExpression::getLeft) + } + + private fun firstDescendantOfTighterPrecedence(expression: JetExpression?, precedence: Int, getChild: JetBinaryExpression.() -> JetExpression?): JetExpression? { + if (expression is JetBinaryExpression) { + val expressionPrecedence = JetPsiPrecedences.getPrecedence(expression) + if (!JetPsiPrecedences.isTighter(expressionPrecedence, precedence)) { + return firstDescendantOfTighterPrecedence(expression.getChild(), precedence, getChild) + } + } + return expression + } } diff --git a/idea/src/org/jetbrains/jet/plugin/util/JetPsiPrecedences.kt b/idea/src/org/jetbrains/jet/plugin/util/JetPsiPrecedences.kt index 78aea90bf25..d36d02727e2 100644 --- a/idea/src/org/jetbrains/jet/plugin/util/JetPsiPrecedences.kt +++ b/idea/src/org/jetbrains/jet/plugin/util/JetPsiPrecedences.kt @@ -61,4 +61,8 @@ public object JetPsiPrecedences { else -> PRECEDENCE_OF_ATOMIC_EXPRESSION } } + + public fun isTighter(subject: Int, tighterThan: Int): Boolean { + return subject < tighterThan + } } diff --git a/idea/testData/intentions/swapBinaryExpression/assignment.kt b/idea/testData/intentions/swapBinaryExpression/assignment.kt new file mode 100644 index 00000000000..cb47f15183f --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/assignment.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +fun main() { + var mutablevar = 20 + mutablevar = 10 +} \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/equals.kt b/idea/testData/intentions/swapBinaryExpression/equals.kt index cb47f15183f..ee7e7625155 100644 --- a/idea/testData/intentions/swapBinaryExpression/equals.kt +++ b/idea/testData/intentions/swapBinaryExpression/equals.kt @@ -1,5 +1,3 @@ -// IS_APPLICABLE: false -fun main() { - var mutablevar = 20 - mutablevar = 10 +fun neq(a: Int, b: Int) { + if (a == b) {} } \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/doubleEquals.kt.after b/idea/testData/intentions/swapBinaryExpression/equals.kt.after similarity index 100% rename from idea/testData/intentions/swapBinaryExpression/doubleEquals.kt.after rename to idea/testData/intentions/swapBinaryExpression/equals.kt.after diff --git a/idea/testData/intentions/swapBinaryExpression/identityEquals.kt b/idea/testData/intentions/swapBinaryExpression/identityEquals.kt new file mode 100644 index 00000000000..be3ec8eaa7f --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/identityEquals.kt @@ -0,0 +1,3 @@ +fun neq(a: Int, b: Int) { + if (a === b) {} +} \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/doubleEquals.kt b/idea/testData/intentions/swapBinaryExpression/identityEquals.kt.after similarity index 50% rename from idea/testData/intentions/swapBinaryExpression/doubleEquals.kt rename to idea/testData/intentions/swapBinaryExpression/identityEquals.kt.after index ee7e7625155..16d59ce04c5 100644 --- a/idea/testData/intentions/swapBinaryExpression/doubleEquals.kt +++ b/idea/testData/intentions/swapBinaryExpression/identityEquals.kt.after @@ -1,3 +1,3 @@ fun neq(a: Int, b: Int) { - if (a == b) {} + if (b === a) {} } \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt b/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt new file mode 100644 index 00000000000..8998a30a32d --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt @@ -0,0 +1,3 @@ +fun neq(a: Int, b: Int) { + if (a identityEquals b) {} +} \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt.after b/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt.after new file mode 100644 index 00000000000..ca6f4f0ab46 --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt.after @@ -0,0 +1,3 @@ +fun neq(a: Int, b: Int) { + if (b identityEquals a) {} +} \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/notIDentityEquals.kt.after b/idea/testData/intentions/swapBinaryExpression/notIDentityEquals.kt.after new file mode 100644 index 00000000000..9e28b22abe5 --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/notIDentityEquals.kt.after @@ -0,0 +1,3 @@ +fun neq(a: Int, b: Int) { + if (b !== a) {} +} \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/notDoubleEquals.kt b/idea/testData/intentions/swapBinaryExpression/notIdentityEquals.kt similarity index 69% rename from idea/testData/intentions/swapBinaryExpression/notDoubleEquals.kt rename to idea/testData/intentions/swapBinaryExpression/notIdentityEquals.kt index a03f7db86f7..82137198028 100644 --- a/idea/testData/intentions/swapBinaryExpression/notDoubleEquals.kt +++ b/idea/testData/intentions/swapBinaryExpression/notIdentityEquals.kt @@ -1,4 +1,3 @@ -// IS_APPLICABLE: false fun neq(a: Int, b: Int) { if (a !== b) {} } \ No newline at end of file diff --git a/idea/testData/intentions/swapBinaryExpression/plusMinus.kt b/idea/testData/intentions/swapBinaryExpression/plusMinus.kt new file mode 100644 index 00000000000..3fdef5e8256 --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/plusMinus.kt @@ -0,0 +1,3 @@ +fun test(): Int { + return 1 + 2 - 3 +} diff --git a/idea/testData/intentions/swapBinaryExpression/plusMinus.kt.after b/idea/testData/intentions/swapBinaryExpression/plusMinus.kt.after new file mode 100644 index 00000000000..28b2cc2611d --- /dev/null +++ b/idea/testData/intentions/swapBinaryExpression/plusMinus.kt.after @@ -0,0 +1,3 @@ +fun test(): Int { + return 2 + 1 - 3 +} diff --git a/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationTestGenerated.java index 822037a79e5..3308fc2d0e9 100644 --- a/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/intentions/CodeTransformationTestGenerated.java @@ -3255,6 +3255,11 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/swapBinaryExpression"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("assignment.kt") + public void testAssignment() throws Exception { + doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/assignment.kt"); + } + @TestMetadata("compareTo.kt") public void testCompareTo() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/compareTo.kt"); @@ -3295,11 +3300,6 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/divideLiteral.kt"); } - @TestMetadata("doubleEquals.kt") - public void testDoubleEquals() throws Exception { - doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/doubleEquals.kt"); - } - @TestMetadata("equals.kt") public void testEquals() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/equals.kt"); @@ -3320,6 +3320,16 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/greaterThanEquals.kt"); } + @TestMetadata("identityEquals.kt") + public void testIdentityEquals() throws Exception { + doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/identityEquals.kt"); + } + + @TestMetadata("identityEqualsLiteral.kt") + public void testIdentityEqualsLiteral() throws Exception { + doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/identityEqualsLiteral.kt"); + } + @TestMetadata("in.kt") public void testIn() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/in.kt"); @@ -3410,16 +3420,16 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/nonBinaryExpr.kt"); } - @TestMetadata("notDoubleEquals.kt") - public void testNotDoubleEquals() throws Exception { - doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/notDoubleEquals.kt"); - } - @TestMetadata("notEquals.kt") public void testNotEquals() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/notEquals.kt"); } + @TestMetadata("notIdentityEquals.kt") + public void testNotIdentityEquals() throws Exception { + doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/notIdentityEquals.kt"); + } + @TestMetadata("notIn.kt") public void testNotIn() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/notIn.kt"); @@ -3455,6 +3465,11 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/plusLiteral.kt"); } + @TestMetadata("plusMinus.kt") + public void testPlusMinus() throws Exception { + doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/plusMinus.kt"); + } + @TestMetadata("rangeTo.kt") public void testRangeTo() throws Exception { doTestSwapBinaryExpression("idea/testData/intentions/swapBinaryExpression/rangeTo.kt");