Apply De Morgan's laws to non-negated binary expressions

So #KT-18460 Fixed
This commit is contained in:
Dmitry Neverov
2017-06-16 12:28:04 +03:00
committed by Mikhail Glukhikh
parent af53a0ecd5
commit 44ce5f73f7
44 changed files with 193 additions and 128 deletions
+1 -1
View File
@@ -1102,7 +1102,7 @@
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertNegatedExpressionWithDemorgansLawIntention</className>
<className>org.jetbrains.kotlin.idea.intentions.ConvertBinaryExpressionWithDemorgansLawIntention</className>
<category>Kotlin</category>
</intentionAction>
@@ -19,46 +19,49 @@ package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import java.util.*
class ConvertNegatedExpressionWithDemorgansLawIntention : SelfTargetingOffsetIndependentIntention<KtPrefixExpression>(KtPrefixExpression::class.java, "DeMorgan Law") {
override fun isApplicableTo(element: KtPrefixExpression): Boolean {
val prefixOperator = element.operationReference.getReferencedNameElementType()
if (prefixOperator != KtTokens.EXCL) return false
class ConvertBinaryExpressionWithDemorgansLawIntention : SelfTargetingOffsetIndependentIntention<KtBinaryExpression>(KtBinaryExpression::class.java, "DeMorgan Law") {
override fun isApplicableTo(element: KtBinaryExpression): Boolean {
val expr = element.parentsWithSelf.takeWhile { it is KtBinaryExpression }.last() as KtBinaryExpression
val parenthesizedExpression = element.baseExpression as? KtParenthesizedExpression
val baseExpression = parenthesizedExpression?.expression as? KtBinaryExpression ?: return false
when (baseExpression.operationToken) {
when (expr.operationToken) {
KtTokens.ANDAND -> text = "Replace '&&' with '||'"
KtTokens.OROR -> text = "Replace '||' with '&&'"
else -> return false
}
return splitBooleanSequence(baseExpression) != null
return splitBooleanSequence(expr) != null
}
override fun applyTo(element: KtPrefixExpression, editor: Editor?) {
override fun applyTo(element: KtBinaryExpression, editor: Editor?) {
applyTo(element)
}
fun applyTo(element: KtPrefixExpression) {
val parenthesizedExpression = element.baseExpression as KtParenthesizedExpression
val baseExpression = parenthesizedExpression.expression as KtBinaryExpression
fun applyTo(element: KtBinaryExpression) {
val expr = element.parentsWithSelf.takeWhile { it is KtBinaryExpression }.last() as KtBinaryExpression
val operatorText = when (baseExpression.operationToken) {
val operatorText = when (expr.operationToken) {
KtTokens.ANDAND -> KtTokens.OROR.value
KtTokens.OROR -> KtTokens.ANDAND.value
else -> throw IllegalArgumentException()
}
val operands = splitBooleanSequence(baseExpression)!!.asReversed()
val operands = splitBooleanSequence(expr)!!.asReversed()
val newExpression = KtPsiFactory(element).buildExpression {
val newExpression = KtPsiFactory(expr).buildExpression {
appendExpressions(operands.map { it.negate() }, separator = operatorText)
}
element.replace(newExpression)
val grandParentPrefix = expr.parent.parent as? KtPrefixExpression
val negated = expr.parent is KtParenthesizedExpression &&
grandParentPrefix?.operationReference?.getReferencedNameElementType() == KtTokens.EXCL
if (negated) {
grandParentPrefix?.replace(newExpression)
} else {
expr.replace(newExpression.negate())
}
}
private fun splitBooleanSequence(expression: KtBinaryExpression): List<KtExpression>? {
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.core.unblockDocument
import org.jetbrains.kotlin.idea.util.CommentSaver
import org.jetbrains.kotlin.idea.util.psi.patternMatching.matches
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -58,9 +59,15 @@ class InvertIfConditionIntention : SelfTargetingIntention<KtIfExpression>(KtIfEx
commentSaver.restore(commentRestoreRange)
val newIfCondition = newIf.condition
val simplifyIntention = ConvertNegatedExpressionWithDemorgansLawIntention()
if (newIfCondition is KtPrefixExpression && simplifyIntention.isApplicableTo(newIfCondition)) {
simplifyIntention.applyTo(newIfCondition)
val simplifyIntention = ConvertBinaryExpressionWithDemorgansLawIntention()
(newIfCondition as? KtPrefixExpression)?.let {
//use De Morgan's law only for negated condition to not make it more complex
if (it.operationReference.getReferencedNameElementType() == KtTokens.EXCL) {
val binaryExpr = (it.baseExpression as? KtParenthesizedExpression)?.expression as? KtBinaryExpression
if (binaryExpr != null && simplifyIntention.isApplicableTo(binaryExpr)) {
simplifyIntention.applyTo(binaryExpr)
}
}
}
editor?.apply {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertBinaryExpressionWithDemorgansLawIntention
@@ -3,5 +3,5 @@ object O {
fun bar(): Boolean = true
}
fun foo(p1: Boolean, p2: Boolean) {
if (<caret>!(O.foo() || O.bar())) return
if (!(<caret>O.foo() || O.bar())) return
}
@@ -3,5 +3,5 @@ object O {
fun bar(): Boolean = true
}
fun foo(p1: Boolean, p2: Boolean) {
if (<caret>!O.foo() && !O.bar()) return
if (!O.foo() && !O.bar()) return
}
@@ -3,5 +3,5 @@ operator fun String.not(): Boolean {
}
fun foo(a: Boolean, b: Boolean) : Boolean {
return !<caret>(!"" || b)
return !(<caret>!"" || b)
}
@@ -1,3 +1,3 @@
fun foo(a: Boolean, b: Boolean, c: Boolean) : Boolean {
return !<caret>(a || !b || c)
return !(<caret>a || !b || c)
}
@@ -1,3 +1,3 @@
fun foo(a: Boolean, b: Boolean) : Boolean {
return !<caret>(a && !b)
return !(<caret>a && !b)
}
@@ -1,4 +1,5 @@
// IS_APPLICABLE: false
// INTENTION_TEXT: Replace '&&' with '||'
fun foo(a: Boolean, b: Boolean) : Boolean {
return <caret>a && b
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '&&' with '||'
fun foo(a: Boolean, b: Boolean) : Boolean {
return !(!a || !b)
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '||' with '&&'
fun foo(a: Boolean, b: Boolean) : Boolean {
return <caret>a || b
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '||' with '&&'
fun foo(a: Boolean, b: Boolean) : Boolean {
return !(!a && !b)
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '||' with '&&'
fun foo(a: Boolean, b: Boolean) : Boolean {
return <caret>!a || !b
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '||' with '&&'
fun foo(a: Boolean, b: Boolean) : Boolean {
return !(a && b)
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '&&' with '||'
fun foo(a: Boolean, b: Boolean) : Boolean {
return <caret>!a && !b
}
@@ -0,0 +1,5 @@
// INTENTION_TEXT: Replace '&&' with '||'
fun foo(a: Boolean, b: Boolean) : Boolean {
return !(a || b)
}
@@ -3,5 +3,5 @@ operator fun Boolean.unaryPlus(): Boolean {
}
fun foo(a: Boolean, b: Boolean) : Boolean {
return !<caret>(+a || b)
return !(<caret>+a || b)
}
@@ -1 +0,0 @@
org.jetbrains.kotlin.idea.intentions.ConvertNegatedExpressionWithDemorgansLawIntention
@@ -1,6 +1,7 @@
// "Replace with safe (?.) call" "false"
// ACTION: Add non-null asserted (!!) call
// ACTION: Flip '>'
// ACTION: Replace '&&' with '||'
// ACTION: Replace overloaded operator with function call
// ACTION: Simplify boolean expression
// ERROR: Operator call corresponds to a dot-qualified call 'w?.x.compareTo(42)' which is not allowed on a nullable receiver 'w?.x'.
@@ -4026,6 +4026,123 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertBinaryExpressionWithDemorgansLaw extends AbstractIntentionTest {
public void testAllFilesPresentInConvertBinaryExpressionWithDemorgansLaw() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("complexNegation1.kt")
public void testComplexNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/complexNegation1.kt");
doTest(fileName);
}
@TestMetadata("complexNegation2.kt")
public void testComplexNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/complexNegation2.kt");
doTest(fileName);
}
@TestMetadata("conjunctionNegation1.kt")
public void testConjunctionNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/conjunctionNegation1.kt");
doTest(fileName);
}
@TestMetadata("conjunctionNegation2.kt")
public void testConjunctionNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/conjunctionNegation2.kt");
doTest(fileName);
}
@TestMetadata("disjunctionNegation1.kt")
public void testDisjunctionNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/disjunctionNegation1.kt");
doTest(fileName);
}
@TestMetadata("disjunctionNegation2.kt")
public void testDisjunctionNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/disjunctionNegation2.kt");
doTest(fileName);
}
@TestMetadata("dontAddRedundantParenthesis.kt")
public void testDontAddRedundantParenthesis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/dontAddRedundantParenthesis.kt");
doTest(fileName);
}
@TestMetadata("doubleNegation.kt")
public void testDoubleNegation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/doubleNegation.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOperator.kt")
public void testInapplicableOperator() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/inapplicableOperator.kt");
doTest(fileName);
}
@TestMetadata("inapplicableTriple.kt")
public void testInapplicableTriple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/inapplicableTriple.kt");
doTest(fileName);
}
@TestMetadata("longMixedExpression.kt")
public void testLongMixedExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/longMixedExpression.kt");
doTest(fileName);
}
@TestMetadata("mixedExpression.kt")
public void testMixedExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/mixedExpression.kt");
doTest(fileName);
}
@TestMetadata("noNegationPrefix1.kt")
public void testNoNegationPrefix1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/noNegationPrefix1.kt");
doTest(fileName);
}
@TestMetadata("noNegationPrefix2.kt")
public void testNoNegationPrefix2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/noNegationPrefix2.kt");
doTest(fileName);
}
@TestMetadata("noNegationPrefix3.kt")
public void testNoNegationPrefix3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/noNegationPrefix3.kt");
doTest(fileName);
}
@TestMetadata("noNegationPrefix4.kt")
public void testNoNegationPrefix4() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/noNegationPrefix4.kt");
doTest(fileName);
}
@TestMetadata("nonstandardPrefixOperator.kt")
public void testNonstandardPrefixOperator() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/nonstandardPrefixOperator.kt");
doTest(fileName);
}
@TestMetadata("retainedParens.kt")
public void testRetainedParens() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertBinaryExpressionWithDemorgansLaw/retainedParens.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/convertCamelCaseTestFunctionToSpaced")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -5001,105 +5118,6 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertNegatedExpressionWithDemorgansLaw extends AbstractIntentionTest {
public void testAllFilesPresentInConvertNegatedExpressionWithDemorgansLaw() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("complexNegation1.kt")
public void testComplexNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/complexNegation1.kt");
doTest(fileName);
}
@TestMetadata("complexNegation2.kt")
public void testComplexNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/complexNegation2.kt");
doTest(fileName);
}
@TestMetadata("conjunctionNegation1.kt")
public void testConjunctionNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/conjunctionNegation1.kt");
doTest(fileName);
}
@TestMetadata("conjunctionNegation2.kt")
public void testConjunctionNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/conjunctionNegation2.kt");
doTest(fileName);
}
@TestMetadata("disjunctionNegation1.kt")
public void testDisjunctionNegation1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/disjunctionNegation1.kt");
doTest(fileName);
}
@TestMetadata("disjunctionNegation2.kt")
public void testDisjunctionNegation2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/disjunctionNegation2.kt");
doTest(fileName);
}
@TestMetadata("dontAddRedundantParenthesis.kt")
public void testDontAddRedundantParenthesis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/dontAddRedundantParenthesis.kt");
doTest(fileName);
}
@TestMetadata("doubleNegation.kt")
public void testDoubleNegation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/doubleNegation.kt");
doTest(fileName);
}
@TestMetadata("inapplicableNormalExpression.kt")
public void testInapplicableNormalExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/inapplicableNormalExpression.kt");
doTest(fileName);
}
@TestMetadata("inapplicableOperator.kt")
public void testInapplicableOperator() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/inapplicableOperator.kt");
doTest(fileName);
}
@TestMetadata("inapplicableTriple.kt")
public void testInapplicableTriple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/inapplicableTriple.kt");
doTest(fileName);
}
@TestMetadata("longMixedExpression.kt")
public void testLongMixedExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/longMixedExpression.kt");
doTest(fileName);
}
@TestMetadata("mixedExpression.kt")
public void testMixedExpression() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/mixedExpression.kt");
doTest(fileName);
}
@TestMetadata("nonstandardPrefixOperator.kt")
public void testNonstandardPrefixOperator() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/nonstandardPrefixOperator.kt");
doTest(fileName);
}
@TestMetadata("retainedParens.kt")
public void testRetainedParens() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertNegatedExpressionWithDemorgansLaw/retainedParens.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/intentions/convertObjectLiteralToClass")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)