KT-4568 Bug Fix: Refactored the convertNegatedExpressionWithDemorgansLaw intention to better handle longer and more complex expressions. Fixed bug and added appropriate test cases as well.
This commit is contained in:
committed by
Mikhael Bogdanov
parent
de273c0418
commit
4de6960443
+48
-30
@@ -26,6 +26,9 @@ import org.jetbrains.jet.lang.psi.JetPsiFactory
|
|||||||
import com.intellij.psi.impl.source.tree.PsiErrorElementImpl
|
import com.intellij.psi.impl.source.tree.PsiErrorElementImpl
|
||||||
import org.jetbrains.jet.lang.psi.JetPsiUtil
|
import org.jetbrains.jet.lang.psi.JetPsiUtil
|
||||||
import org.jetbrains.jet.lang.psi.JetExpression
|
import org.jetbrains.jet.lang.psi.JetExpression
|
||||||
|
import org.jetbrains.jet.lang.psi.JetConstantExpression
|
||||||
|
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
public class ConvertNegatedExpressionWithDemorgansLawIntention : JetSelfTargetingIntention<JetPrefixExpression>(
|
public class ConvertNegatedExpressionWithDemorgansLawIntention : JetSelfTargetingIntention<JetPrefixExpression>(
|
||||||
"convert.negated.expression.with.demorgans.law",javaClass()
|
"convert.negated.expression.with.demorgans.law",javaClass()
|
||||||
@@ -36,50 +39,65 @@ public class ConvertNegatedExpressionWithDemorgansLawIntention : JetSelfTargetin
|
|||||||
if (prefixOperator != JetTokens.EXCL) return false
|
if (prefixOperator != JetTokens.EXCL) return false
|
||||||
|
|
||||||
val parenthesizedExpression = element.getBaseExpression() as? JetParenthesizedExpression
|
val parenthesizedExpression = element.getBaseExpression() as? JetParenthesizedExpression
|
||||||
var binaryElement = parenthesizedExpression?.getExpression() as? JetBinaryExpression
|
val baseExpression = parenthesizedExpression?.getExpression() as? JetBinaryExpression ?: return false
|
||||||
val operatorToken = binaryElement?.getOperationToken() ?: return false
|
val operatorToken = baseExpression.getOperationToken() ?: return false
|
||||||
|
|
||||||
if (!(operatorToken == JetTokens.ANDAND || operatorToken == JetTokens.OROR)) {
|
if (!(operatorToken == JetTokens.ANDAND || operatorToken == JetTokens.OROR)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
while (binaryElement != null) { // While loop lets us handle expressions of any length
|
val elements = splitBooleanSequence(baseExpression) ?: return false
|
||||||
val leftChild = binaryElement?.getLeft()
|
return !(elements.any { x -> x is PsiErrorElementImpl })
|
||||||
val rightChild = binaryElement?.getRight()
|
|
||||||
if (rightChild is PsiErrorElementImpl || operatorToken != binaryElement?.getOperationToken()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
binaryElement = leftChild as? JetBinaryExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun applyTo(element: JetPrefixExpression, editor: Editor) {
|
override fun applyTo(element: JetPrefixExpression, editor: Editor) {
|
||||||
val parenthesizedExpression = element.getBaseExpression() as JetParenthesizedExpression
|
val parenthesizedExpression = element.getBaseExpression() as JetParenthesizedExpression
|
||||||
var binaryElement = parenthesizedExpression.getExpression() as? JetBinaryExpression
|
val baseExpression = parenthesizedExpression.getExpression() as JetBinaryExpression
|
||||||
val operator = binaryElement!!.getOperationToken()
|
val operatorText = when(baseExpression.getOperationToken()){
|
||||||
val operatorText = when(operator){
|
|
||||||
JetTokens.ANDAND -> JetTokens.OROR.getValue()
|
JetTokens.ANDAND -> JetTokens.OROR.getValue()
|
||||||
JetTokens.OROR -> JetTokens.ANDAND.getValue()
|
JetTokens.OROR -> JetTokens.ANDAND.getValue()
|
||||||
else -> throw IllegalArgumentException("Invalid operator: '$operator'. Only expressions using '&&' or '||' can be converted.")
|
else -> throw IllegalArgumentException(
|
||||||
|
"Invalid operator: '${baseExpression.getOperationToken()}'. Only expressions using '&&' or '||' can be converted.")
|
||||||
}
|
}
|
||||||
var expressionText = ""
|
val elements = splitBooleanSequence(baseExpression)
|
||||||
|
val negatedElements = elements!!.map { exp -> handleSpecial(exp) }
|
||||||
|
val negatedExpression = negatedElements.subList(0,negatedElements.size()-1).foldRight(
|
||||||
|
"${negatedElements.last()}",{negated, exp-> "$exp $operatorText $negated"})
|
||||||
|
|
||||||
while (binaryElement != null) {
|
val newExpression = JetPsiFactory.createExpression(element.getProject(),negatedExpression)
|
||||||
val leftChild = binaryElement?.getLeft()
|
|
||||||
val rightChild = binaryElement?.getRight()
|
|
||||||
|
|
||||||
val rightChildText = rightChild!!.getText()
|
|
||||||
expressionText = "$operatorText !$rightChildText $expressionText "
|
|
||||||
if (leftChild !is JetBinaryExpression) {
|
|
||||||
expressionText = "!${leftChild!!.getText()} $expressionText"
|
|
||||||
}
|
|
||||||
binaryElement = leftChild as? JetBinaryExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
val newExpression = JetPsiFactory.createExpression(element.getProject(),expressionText)
|
|
||||||
element.replace(newExpression)
|
element.replace(newExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleSpecial(expression: JetExpression) : String {
|
||||||
|
return when(expression) {
|
||||||
|
JetTokens.TRUE_KEYWORD -> JetTokens.FALSE_KEYWORD.getValue()
|
||||||
|
JetTokens.FALSE_KEYWORD -> JetTokens.TRUE_KEYWORD.getValue()
|
||||||
|
is JetSimpleNameExpression, is JetConstantExpression, is JetPrefixExpression,
|
||||||
|
is JetParenthesizedExpression -> "!${expression.getText()}"
|
||||||
|
else -> "!(${expression.getText()})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun splitBooleanSequence(expression: JetBinaryExpression) : List<JetExpression>? {
|
||||||
|
val itemList = ArrayList<JetExpression>()
|
||||||
|
val firstOperator = expression.getOperationToken()
|
||||||
|
var currentItem : JetExpression? = expression
|
||||||
|
|
||||||
|
while(currentItem as? JetBinaryExpression != null) {
|
||||||
|
val remainingExpression = currentItem as JetBinaryExpression
|
||||||
|
val operation = remainingExpression.getOperationToken()
|
||||||
|
if (!(operation == JetTokens.ANDAND || operation == JetTokens.OROR)) break
|
||||||
|
|
||||||
|
if (operation != firstOperator) return null //Boolean sequence must be homogenous
|
||||||
|
|
||||||
|
val leftChild = remainingExpression.getLeft()
|
||||||
|
val rightChild = remainingExpression.getRight()
|
||||||
|
itemList.add(rightChild as JetExpression)
|
||||||
|
currentItem = leftChild
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentItem != null) itemList.add(currentItem!!)
|
||||||
|
return itemList
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(a: Boolean, c: Int, d: Int) : Boolean {
|
||||||
|
return !(a <caret>&& c == d)
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(a: Boolean, c: Int, d: Int) : Boolean {
|
||||||
|
return !a || !(c == d)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(a: Int, b: Int, c: Int, d: Int) : Boolean {
|
||||||
|
return !(a < b && c ==<caret> d && a < d)
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(a: Int, b: Int, c: Int, d: Int) : Boolean {
|
||||||
|
return !(a < b) || !(c == d) || !(a < d)
|
||||||
|
}
|
||||||
@@ -3023,6 +3023,16 @@ public class CodeTransformationTestGenerated extends AbstractCodeTransformationT
|
|||||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw"), Pattern.compile("^(.+)\\.kt$"), true);
|
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("complexNegation1.kt")
|
||||||
|
public void testComplexNegation1() throws Exception {
|
||||||
|
doTestConvertNegatedExpressionWithDemorgansLaw("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/complexNegation1.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("complexNegation2.kt")
|
||||||
|
public void testComplexNegation2() throws Exception {
|
||||||
|
doTestConvertNegatedExpressionWithDemorgansLaw("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/complexNegation2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("conjunctionNegation1.kt")
|
@TestMetadata("conjunctionNegation1.kt")
|
||||||
public void testConjunctionNegation1() throws Exception {
|
public void testConjunctionNegation1() throws Exception {
|
||||||
doTestConvertNegatedExpressionWithDemorgansLaw("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/conjunctionNegation1.kt");
|
doTestConvertNegatedExpressionWithDemorgansLaw("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/conjunctionNegation1.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user