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) : DiktatRule( */ internal inner class ExpressionsReplacement { private val expressionToToken: HashMap = LinkedHashMap() + private val tokenToExpression: HashMap = HashMap() private val tokenToOrderedToken: HashMap = HashMap() /** @@ -206,8 +208,14 @@ class BooleanExpressionsRule(configRules: List) : 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) : 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) : 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) : 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 `{ }` 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 + } +}