org.sonar.java.checks.BooleanLiteralCheck Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.QuickFixHelper;
import org.sonar.java.reporting.AnalyzerMessage;
import org.sonar.java.reporting.JavaQuickFix;
import org.sonar.java.reporting.JavaTextEdit;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.ConditionalExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.ParenthesizedTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import static org.sonar.java.reporting.AnalyzerMessage.textSpanBetween;
@Rule(key = "S1125")
public class BooleanLiteralCheck extends IssuableSubscriptionVisitor {
private static final String FALSE_LITERAL = "false";
private static final String TRUE_LITERAL = "true";
@Override
public List nodesToVisit() {
return Arrays.asList(Kind.EQUAL_TO, Kind.NOT_EQUAL_TO, Kind.CONDITIONAL_AND, Kind.CONDITIONAL_OR,
Kind.LOGICAL_COMPLEMENT, Kind.CONDITIONAL_EXPRESSION);
}
@Override
public void visitNode(Tree tree) {
List literalList;
if (tree.is(Kind.LOGICAL_COMPLEMENT)) {
literalList = getBooleanLiterals(((UnaryExpressionTree) tree).expression());
} else if (tree.is(Kind.CONDITIONAL_EXPRESSION)) {
ConditionalExpressionTree expression = (ConditionalExpressionTree) tree;
literalList = getBooleanLiterals(expression.trueExpression(), expression.falseExpression());
} else {
BinaryExpressionTree expression = (BinaryExpressionTree) tree;
literalList = getBooleanLiterals(expression.leftOperand(), expression.rightOperand());
}
int nLiterals = literalList.size();
if (nLiterals > 0) {
QuickFixHelper.newIssue(context)
.forRule(this)
.onTree(literalList.get(0))
.withMessage("Remove the unnecessary boolean literal%s.", nLiterals > 1 ? "s" : "")
.withSecondaries(literalList.stream().skip(1).map(lit -> new JavaFileScannerContext.Location("", lit)).toList())
.withQuickFixes(() -> getQuickFix(tree))
.report();
}
}
private static List getBooleanLiterals(Tree... trees) {
List booleanLiterals = new ArrayList<>();
for (Tree t : trees) {
if (t.is(Kind.NULL_LITERAL)) {
return Collections.emptyList();
} else if (t.is(Kind.BOOLEAN_LITERAL)) {
booleanLiterals.add((LiteralTree) t);
}
}
return booleanLiterals;
}
private static List getQuickFix(Tree tree) {
List edits;
if (tree.is(Kind.CONDITIONAL_EXPRESSION)) {
edits = editsForConditionalExpression((ConditionalExpressionTree) tree);
} else if (tree.is(Kind.LOGICAL_COMPLEMENT)) {
String booleanValue = ((LiteralTree) ((UnaryExpressionTree) tree).expression()).value();
edits = new ArrayList<>();
edits.add(JavaTextEdit.replaceTree(tree, TRUE_LITERAL.equals(booleanValue) ? FALSE_LITERAL : TRUE_LITERAL));
} else if (tree.is(Kind.EQUAL_TO)) {
edits = editsForEquality((BinaryExpressionTree) tree, true);
} else if (tree.is(Kind.NOT_EQUAL_TO)) {
edits = editsForEquality((BinaryExpressionTree) tree, false);
} else if (tree.is(Kind.CONDITIONAL_OR)) {
edits = editsForConditional((BinaryExpressionTree) tree, true);
} else {
// Kind.CONDITIONAL_AND
edits = editsForConditional((BinaryExpressionTree) tree, false);
}
if (edits.isEmpty()) {
return Collections.emptyList();
}
return Collections.singletonList(JavaQuickFix.newQuickFix("Simplify the expression").addTextEdits(edits).build());
}
private static List editsForConditionalExpression(ConditionalExpressionTree tree) {
List edits = new ArrayList<>();
Boolean left = getBooleanValue(tree.trueExpression());
Boolean right = getBooleanValue(tree.falseExpression());
if (left != null) {
if (right != null) {
edits = editsForConditionalBothLiterals(tree, left, right);
} else {
if (left) {
// cond() ? true : expr --> cond() || expr
edits.add(JavaTextEdit.replaceBetweenTree(tree.questionToken(), tree.colonToken(), "||"));
} else {
// cond() ? false : expr --> !cond() && expr
edits.add(JavaTextEdit.replaceBetweenTree(tree.questionToken(), tree.colonToken(), "&&"));
List collection = computeNegatingTextEdits(tree.condition(), true);
edits.addAll(collection);
}
}
} else if (right != null) {
// Defensive programming, if we reached this point, right must be a boolean literal
edits.add(JavaTextEdit.removeTextSpan(textSpanBetween(tree.trueExpression(), false, tree.falseExpression(), true)));
String operator;
if (right) {
// cond() ? expr : true --> !cond() || expr
operator = "||";
edits.add(JavaTextEdit.insertBeforeTree(tree.condition(), "!"));
} else {
// cond() ? expr : false --> cond() && expr
operator = "&&";
}
edits.add(JavaTextEdit.replaceTree(tree.questionToken(), operator));
}
return edits;
}
private static List computeNegatingTextEdits(ExpressionTree tree, boolean followedByConjunction) {
List edits = new ArrayList<>();
if (tree.is(Kind.PARENTHESIZED_EXPRESSION)) {
ParenthesizedTree expression = (ParenthesizedTree) tree;
edits.addAll(computeNegatingTextEdits(expression.expression(), false));
} else if (tree.is(Kind.EQUAL_TO)) {
BinaryExpressionTree condition = (BinaryExpressionTree) tree;
edits.add(JavaTextEdit.replaceTree(condition.operatorToken(), "!="));
} else if (tree.is(Kind.NOT_EQUAL_TO)) {
BinaryExpressionTree condition = (BinaryExpressionTree) tree;
edits.add(JavaTextEdit.replaceTree(condition.operatorToken(), "=="));
} else if (tree.is(Kind.CONDITIONAL_AND)) {
BinaryExpressionTree condition = (BinaryExpressionTree) tree;
if (followedByConjunction) {
edits.add(JavaTextEdit.insertAfterTree(tree, ")"));
}
edits.addAll(computeNegatingTextEdits(condition.rightOperand(), followedByConjunction));
edits.add(JavaTextEdit.replaceTree(condition.operatorToken(), "||"));
edits.addAll(computeNegatingTextEdits(condition.leftOperand(), false));
if (followedByConjunction) {
edits.add(JavaTextEdit.insertBeforeTree(tree, "("));
}
} else if (tree.is(Kind.CONDITIONAL_OR)) {
BinaryExpressionTree condition = (BinaryExpressionTree) tree;
edits.addAll(computeNegatingTextEdits(condition.rightOperand(), followedByConjunction));
edits.add(JavaTextEdit.replaceTree(condition.operatorToken(), "&&"));
edits.addAll(computeNegatingTextEdits(condition.leftOperand(), true));
} else {
edits.add(JavaTextEdit.insertBeforeTree(tree, "!"));
}
return edits;
}
private static List editsForConditionalBothLiterals(ConditionalExpressionTree tree, Boolean left, Boolean right) {
List edits = new ArrayList<>();
// Both side are literals.
JavaTextEdit editRemoveExpressions = JavaTextEdit.removeTextSpan(textSpanBetween(tree.condition(), false, tree.falseExpression(), true));
if (left && !right) {
// cond() ? true : false --> cond()
edits.add(editRemoveExpressions);
} else if (!left && right) {
// cond() ? false : true --> !cond()
edits.add(editRemoveExpressions);
edits.add(JavaTextEdit.insertBeforeTree(tree, "!"));
}
// In case of "true : true" or "false : false", we do not add a quick fix as it looks like a bug (see S3923).
return edits;
}
private static List editsForEquality(BinaryExpressionTree tree, boolean equalToOperator) {
List edits = new ArrayList<>();
ExpressionTree leftOperand = tree.leftOperand();
ExpressionTree rightOperand = tree.rightOperand();
Boolean left = getBooleanValue(leftOperand);
Boolean right = getBooleanValue(rightOperand);
if (left != null) {
if (right != null) {
edits.add(editForEqualityWhenBothLiterals(tree, left, right, equalToOperator));
} else {
// Presence of "!" is deducted from the inverse of the operator value.
if (!left) {
// false == expr -> !expr, false != expr --> expr
equalToOperator = !equalToOperator;
}
edits.add(JavaTextEdit.replaceTextSpan(textSpanBetween(leftOperand, true, rightOperand, false), equalToOperator ? "" : "!"));
}
} else if (right != null) {
// Defensive programming, if we reached this point, right must be a boolean literal
edits = editsForEqualityWhenRightIsLiteral(right, leftOperand, rightOperand, equalToOperator);
}
return edits;
}
private static JavaTextEdit editForEqualityWhenBothLiterals(BinaryExpressionTree tree, Boolean left, Boolean right, boolean equalToOperator) {
if (!left.equals(right)) {
// left and right are not the same, simplification is the inverse of the operator value.
// true == false --> false, false == true --> false, true != false --> true, false != true --> true
equalToOperator = !equalToOperator;
}
// left and right are the same, simplification can be deducted thanks to the operator value.
return JavaTextEdit.replaceTree(tree, equalToOperator ? TRUE_LITERAL : FALSE_LITERAL);
}
private static List editsForEqualityWhenRightIsLiteral(Boolean right, ExpressionTree leftOperand, ExpressionTree rightOperand, boolean equalToOperator) {
List edits = new ArrayList<>();
// Right operand is a literal
if (!right.equals(equalToOperator)) {
// expr == false or expr != true --> !expr
edits.add(JavaTextEdit.insertBeforeTree(leftOperand, "!"));
}
edits.add(JavaTextEdit.removeTextSpan(textSpanBetween(leftOperand, false, rightOperand, true)));
return edits;
}
private static List editsForConditional(BinaryExpressionTree tree, boolean conditionalOr) {
List edits = new ArrayList<>();
ExpressionTree leftOperand = tree.leftOperand();
ExpressionTree rightOperand = tree.rightOperand();
Boolean left = getBooleanValue(leftOperand);
Boolean right = getBooleanValue(rightOperand);
if (left != null) {
if (right != null) {
edits.add(editForConditionalWhenBothLiterals(tree, left, right, conditionalOr));
} else {
AnalyzerMessage.TextSpan textSpanToRemove;
if (conditionalOr == left) {
// true || var --> true or false && var --> false
textSpanToRemove = textSpanBetween(leftOperand, false, rightOperand, true);
} else {
// false || var --> var or true && var --> var
textSpanToRemove = textSpanBetween(leftOperand, true, rightOperand, false);
}
edits.add(JavaTextEdit.removeTextSpan(textSpanToRemove));
}
} else if (right != null) {
// Defensive programming, if we reached this point, right must be a boolean literal
editForConditionalWhenRightIsLiteral(right, leftOperand, rightOperand, conditionalOr)
.ifPresent(edits::add);
}
return edits;
}
@Nullable
private static Boolean getBooleanValue(Tree expression) {
if (expression.is(Kind.BOOLEAN_LITERAL)) {
return Boolean.parseBoolean(((LiteralTree) expression).value());
}
return null;
}
private static JavaTextEdit editForConditionalWhenBothLiterals(BinaryExpressionTree tree, Boolean left, Boolean right, boolean conditionalOr) {
boolean conditionalAnd = !conditionalOr;
boolean simplification =
// true || true or true || false or false || true --> true
(conditionalOr && (left || right))
// true && true --> true
|| (conditionalAnd && left && right);
return JavaTextEdit.replaceTree(tree, simplification ? TRUE_LITERAL : FALSE_LITERAL);
}
private static Optional editForConditionalWhenRightIsLiteral(Boolean right, ExpressionTree leftOperand, ExpressionTree rightOperand, boolean conditionalOr) {
AnalyzerMessage.TextSpan textSpanToRemove;
if (right.equals(conditionalOr)) {
// var || true --> true or var && false --> false
if (mayHaveSideEffect(leftOperand)) {
// Can not remove a tree that could have side effect. We do not suggest to extract the side effect.
return Optional.empty();
}
textSpanToRemove = textSpanBetween(leftOperand, true, rightOperand, false);
} else {
// var || false or var && true --> var
textSpanToRemove = textSpanBetween(leftOperand, false, rightOperand, true);
}
return Optional.of(JavaTextEdit.removeTextSpan(textSpanToRemove));
}
private static boolean mayHaveSideEffect(Tree tree) {
MethodInvocationFinder methodInvocationFinder = new MethodInvocationFinder();
tree.accept(methodInvocationFinder);
return methodInvocationFinder.found;
}
private static class MethodInvocationFinder extends BaseTreeVisitor {
boolean found = false;
@Override
public void visitMethodInvocation(MethodInvocationTree tree) {
found = true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy