
org.sonar.java.checks.tests.BooleanOrNullLiteralInAssertionsCheck Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2025 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.tests;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.sonar.check.Rule;
import org.sonar.java.checks.methods.AbstractMethodDetection;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeCastTree;
import java.util.List;
import java.util.Optional;
@Rule(key = "S2701")
public class BooleanOrNullLiteralInAssertionsCheck extends AbstractMethodDetection {
private static final String DEFAULT_MESSAGE = "Remove or correct this assertion.";
private static final String MESSAGE_WITH_ALTERNATIVE = "Use %s instead.";
private static final String ASSERT = "assert";
private static final String IS = "is";
private static final MethodMatchers FEST_ASSERT_THAT = MethodMatchers.create()
.ofTypes("org.fest.assertions.Assertions")
.names("assertThat")
.addParametersMatcher(MethodMatchers.ANY)
.build();
@Override
protected MethodMatchers getMethodInvocationMatchers() {
return MethodMatchers.or(
MethodMatchers.create()
.ofTypes(
"org.junit.Assert",
"org.junit.jupiter.api.Assertions",
"junit.framework.Assert",
"junit.framework.TestCase")
.name(name -> name.startsWith(ASSERT))
.withAnyParameters()
.build(),
MethodMatchers.create()
.ofSubTypes("org.fest.assertions.GenericAssert")
.name(name -> name.startsWith(IS))
.withAnyParameters()
.build()
);
}
@Override
protected void onMethodInvocationFound(MethodInvocationTree mit) {
switch (mit.methodSymbol().name()) {
case "assertEquals",
"assertSame":
checkEqualityAsserts(mit, false);
break;
case "assertNotEquals",
"assertNotSame":
checkEqualityAsserts(mit, true);
break;
case "isEqualTo",
"isSameAs":
checkFestEqualityAsserts(mit, false);
break;
case "isNotEqualTo",
"isNotSameAs":
checkFestEqualityAsserts(mit, true);
break;
default:
checkOtherAsserts(mit);
break;
}
}
private void checkEqualityAsserts(MethodInvocationTree mit, boolean flipped) {
List literals = findLiterals(mit.arguments());
IdentifierTree methodName = ExpressionUtils.methodName(mit);
if (literals.size() > 1) {
reportDefaultMessage(methodName, literals);
} else if (literals.size() == 1) {
checkEqualityAssertWithOneLiteral(methodName, literals.get(0), flipped, ASSERT);
}
}
private void checkFestEqualityAsserts(MethodInvocationTree mit, boolean flipped) {
if (mit.arguments().isEmpty()) {
return;
}
Optional expectedLiteral = getBoolOrNullLiteral(mit.arguments().get(0));
Optional actualLiteral = findActualLiteralForFest(mit);
IdentifierTree methodName = ExpressionUtils.methodName(mit);
if (expectedLiteral.isPresent() && actualLiteral.isPresent()) {
reportDefaultMessage(methodName, Arrays.asList(expectedLiteral.get(), actualLiteral.get()));
} else {
expectedLiteral.ifPresent(literal -> checkEqualityAssertWithOneLiteral(methodName, literal, flipped, IS));
actualLiteral.ifPresent(literal -> checkEqualityAssertWithOneLiteral(methodName, literal, flipped, IS));
}
}
private void checkEqualityAssertWithOneLiteral(IdentifierTree methodName, LiteralTree literal, boolean flipped, String assertOrIs) {
String predicate;
if (literal.is(Tree.Kind.NULL_LITERAL)) {
predicate = flipped ? "NotNull" : "Null";
} else {
Optional value = literal.asConstant(Boolean.class);
if (!value.isPresent()) {
return;
}
if (Boolean.TRUE.equals(value.get())) {
predicate = flipped ? "False" : "True";
} else {
predicate = flipped ? "True" : "False";
}
}
String recommendedAssertMethod = assertOrIs + predicate;
List secondaryLocation = Collections.singletonList(
new JavaFileScannerContext.Location("This literal can be avoided by using a different assertion method.", literal)
);
String mainMessage = String.format(MESSAGE_WITH_ALTERNATIVE, recommendedAssertMethod);
reportIssue(methodName, mainMessage, secondaryLocation, null);
}
private void checkOtherAsserts(MethodInvocationTree mit) {
List literals = findLiterals(mit.arguments());
Optional festActualLiteral = findActualLiteralForFest(mit);
festActualLiteral.ifPresent(literals::add);
if (!literals.isEmpty()) {
reportDefaultMessage(ExpressionUtils.methodName(mit), literals);
}
}
private static List findLiterals(List expressions) {
List result = new ArrayList<>();
for (ExpressionTree expression : expressions) {
getBoolOrNullLiteral(expression).ifPresent(result::add);
}
return result;
}
private static Optional findActualLiteralForFest(MethodInvocationTree mit) {
if (FEST_ASSERT_THAT.matches(mit)) {
return getBoolOrNullLiteral(mit.arguments().get(0));
}
if (mit.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
MemberSelectExpressionTree member = (MemberSelectExpressionTree) mit.methodSelect();
if (member.expression().is(Tree.Kind.METHOD_INVOCATION)) {
return findActualLiteralForFest((MethodInvocationTree) member.expression());
}
}
return Optional.empty();
}
/**
* Tests whether an expression is a boolean or null literal, possibly embedded in a sequence of casts (because null
* literals often need to be cast to avoid overloading ambiguities), and return the null literal if so.
*/
private static Optional getBoolOrNullLiteral(ExpressionTree expr) {
if (expr.is(Tree.Kind.TYPE_CAST)) {
return getBoolOrNullLiteral(((TypeCastTree) expr).expression());
} else if (expr.is(Tree.Kind.NULL_LITERAL) || expr.is(Tree.Kind.BOOLEAN_LITERAL)) {
return Optional.of((LiteralTree) expr);
} else {
return Optional.empty();
}
}
private void reportDefaultMessage(IdentifierTree methodName, List literals) {
List literalLocations = literals.stream()
.map(literal -> new JavaFileScannerContext.Location("There does not seem to be a reason to use a literal here.", literal))
.toList();
reportIssue(methodName, DEFAULT_MESSAGE, literalLocations, null);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy