Repository: analysis-dev/diktat (kotlin)

Issue:
Small issue on boolean expressions simplification rule (De Morgan's law)
```
if (!(this.valueParameters[i].getFunctionName() == other.valueParameters[i].getFunctionName() &&
                    this.valueParameters[i].getFunctionType() == other.valueParameters[i].getFunctionType())
) {
```

is fixed to 

```
 fun foo() {
    if (!this.valueParameters[i].getFunctionName() == other.valueParameters[i].getFunctionName() || !this.valueParameters[i].getFunctionType() == other.valueParameters[i].getFunctionType()
    ) {
        return false
    }
}
```

And diktat forgets to add braces.

Pull request:
### What's done:
* added test

It closes #1521

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt
index 10b4e0394e..a793605c87 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BooleanExpressionsRule.kt
@@ -5,6 +5,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.COMPLEX_BOOLEAN_EXPRESSION
 import org.cqfn.diktat.ruleset.rules.DiktatRule
 import org.cqfn.diktat.ruleset.utils.KotlinParser
 import org.cqfn.diktat.ruleset.utils.findAllNodesWithCondition
+import org.cqfn.diktat.ruleset.utils.logicalInfixMethodMapping
 import org.cqfn.diktat.ruleset.utils.logicalInfixMethods
 import com.bpodgursky.jbool_expressions.Expression
 import com.bpodgursky.jbool_expressions.options.ExprOptions
@@ -177,6 +178,7 @@ class BooleanExpressionsRule(configRules: List<RulesConfig>) : DiktatRule(
      */
     internal inner class ExpressionsReplacement {
         private val expressionToToken: HashMap<String, String> = LinkedHashMap()
+        private val tokenToExpression: HashMap<String, String> = HashMap()
         private val tokenToOrderedToken: HashMap<String, String> = HashMap()
 
         /**
@@ -206,8 +208,14 @@ class BooleanExpressionsRule(configRules: List<RulesConfig>) : DiktatRule(
         fun addExpression(expressionAstNode: ASTNode) {
             val expressionText = expressionAstNode.textWithoutComments()
             // support case when `boolean_expression` matches to `!boolean_expression`
-            val expression = if (expressionText.startsWith('!')) expressionText.substring(1) else expressionText
-            getLetter(expressionToToken, expression)
+            val (expression, negativeExpression) = if (expressionText.startsWith('!')) {
+                expressionText.substring(1) to expressionText
+            } else {
+                expressionText to getNegativeExpression(expressionAstNode, expressionText)
+            }
+            val letter = getLetter(expressionToToken, expression)
+            tokenToExpression["!$letter"] = negativeExpression
+            tokenToExpression[letter] = expression
         }
 
         /**
@@ -216,13 +224,12 @@ class BooleanExpressionsRule(configRules: List<RulesConfig>) : DiktatRule(
          * @param fullExpression full boolean expression in kotlin
          * @return full expression in jbool format
          */
-        @Suppress("UnsafeCallOnNullableType")
         fun replaceExpressions(fullExpression: String): String {
             var resultExpression = fullExpression
             expressionToToken.keys
                 .sortedByDescending { it.length }
                 .forEach { refExpr ->
-                    resultExpression = resultExpression.replace(refExpr, expressionToToken[refExpr]!!)
+                    resultExpression = resultExpression.replace(refExpr, expressionToToken.getValue(refExpr))
                 }
             return resultExpression
         }
@@ -241,10 +248,10 @@ class BooleanExpressionsRule(configRules: List<RulesConfig>) : DiktatRule(
             }
             resultExpression = resultExpression.format(args = tokenToOrderedToken.keys.toTypedArray())
             // restore expression
-            expressionToToken.values.forEachIndexed { index, value ->
+            tokenToExpression.keys.forEachIndexed { index, value ->
                 resultExpression = resultExpression.replace(value, "%${index + 1}\$s")
             }
-            resultExpression = resultExpression.format(args = expressionToToken.keys.toTypedArray())
+            resultExpression = resultExpression.format(args = tokenToExpression.values.toTypedArray())
             return resultExpression
         }
 
@@ -252,6 +259,16 @@ class BooleanExpressionsRule(configRules: List<RulesConfig>) : DiktatRule(
             .computeIfAbsent(key) {
                 ('A'.code + letters.size).toChar().toString()
             }
+
+        private fun getNegativeExpression(expressionAstNode: ASTNode, expression: String): String =
+            if (expressionAstNode.elementType == BINARY_EXPRESSION) {
+                val operation = (expressionAstNode.psi as KtBinaryExpression).operationReference.text
+                logicalInfixMethodMapping[operation]?.let {
+                    expression.replace(operation, it)
+                } ?: "!($expression)"
+            } else {
+                "!$expression"
+            }
     }
 
     companion object {
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
index f6637ac519..b7eb229084 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
@@ -22,10 +22,24 @@ internal const val EMPTY_BLOCK_TEXT = "{}"
  */
 internal val standardMethods = listOf("main", "equals", "hashCode", "toString", "clone", "finalize")
 
+/**
+ * Mapping (value is negative infix) of infix methods that return Boolean
+ */
+internal val logicalInfixMethodMapping = mapOf(
+    "==" to "!=",
+    "!=" to "==",
+    ">" to "<=",
+    "<" to ">=",
+    ">=" to "<",
+    "<=" to ">",
+    "in" to "!in",
+    "!in" to "in",
+)
+
 /**
  * List of infix methods that return Boolean
  */
-internal val logicalInfixMethods = setOf("==", "!=", ">", "<", ">=", "<=", "in", "!in", "xor")
+internal val logicalInfixMethods = logicalInfixMethodMapping.keys + "xor"
 
 /**
  * List of element types present in empty code block `{ }`


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BooleanExpressionsRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BooleanExpressionsRuleFixTest.kt
index f5f4d0722b..fbd015f7a5 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BooleanExpressionsRuleFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BooleanExpressionsRuleFixTest.kt
@@ -43,4 +43,10 @@ class BooleanExpressionsRuleFixTest : FixTestBase("test/paragraph3/boolean_expre
     fun `check handling of negative expression`() {
         fixAndCompare("NegativeExpressionExpected.kt", "NegativeExpressionTest.kt")
     }
+
+    @Test
+    @Tag(WarningNames.COMPLEX_BOOLEAN_EXPRESSION)
+    fun `check expression simplification`() {
+        fixAndCompare("ExpressionSimplificationExpected.kt", "ExpressionSimplificationTest.kt")
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationExpected.kt
new file mode 100644
index 0000000000..5530dfea07
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationExpected.kt
@@ -0,0 +1,22 @@
+package test.paragraph3.boolean_expressions
+
+fun F.foo1() {
+    if (this.valueParameters[i].getFunctionName() != other.valueParameters[i].getFunctionName() || this.valueParameters[i].getFunctionType() == other.valueParameters[i].getFunctionType()
+    ) {
+        return false
+    }
+}
+
+fun F.foo2() {
+    if (this.valueParameters[i].getFunctionName() <= other.valueParameters[i].getFunctionName() || this.valueParameters[i].getFunctionType() >= other.valueParameters[i].getFunctionType()
+    ) {
+        return false
+    }
+}
+
+fun F.foo3() {
+    if (!(this.valueParameters[i].getFunctionName() xor other.valueParameters[i].getFunctionName()) || !(this.valueParameters[i].getFunctionType() xor other.valueParameters[i].getFunctionType())
+    ) {
+        return false
+    }
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationTest.kt b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationTest.kt
new file mode 100644
index 0000000000..2e6c225278
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/boolean_expressions/ExpressionSimplificationTest.kt
@@ -0,0 +1,25 @@
+package test.paragraph3.boolean_expressions
+
+fun F.foo1() {
+    if (!(this.valueParameters[i].getFunctionName() == other.valueParameters[i].getFunctionName() &&
+                this.valueParameters[i].getFunctionType() != other.valueParameters[i].getFunctionType())
+    ) {
+        return false
+    }
+}
+
+fun F.foo2() {
+    if (!(this.valueParameters[i].getFunctionName() > other.valueParameters[i].getFunctionName() &&
+                this.valueParameters[i].getFunctionType() < other.valueParameters[i].getFunctionType())
+    ) {
+        return false
+    }
+}
+
+fun F.foo3() {
+    if (!(this.valueParameters[i].getFunctionName() xor other.valueParameters[i].getFunctionName() &&
+                this.valueParameters[i].getFunctionType() xor other.valueParameters[i].getFunctionType())
+    ) {
+        return false
+    }
+}