
org.sonar.java.checks.tests.AssertJAssertionsInConsumerCheck 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.tests;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.AbstractAssertionVisitor;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.JavaFileScannerContext.Location;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import static java.util.Collections.singletonList;
@Rule(key = "S6103")
public class AssertJAssertionsInConsumerCheck extends IssuableSubscriptionVisitor {
private static final String ORG_ASSERTJ_CORE_API_ABSTRACT_ASSERT = "org.assertj.core.api.AbstractAssert";
private static final String JAVA_UTIL_FUNCTION_CONSUMER = "java.util.function.Consumer";
private static final String ORG_ASSERTJ_CORE_API_THROWING_CONSUMER = "org.assertj.core.api.ThrowingConsumer";
private static final String ORG_ASSERTJ_CORE_API_THROWING_CONSUMER_ARRAY = "org.assertj.core.api.ThrowingConsumer[]";
private static final MethodMatchers METHODS_WITH_CONSUMER_AT_INDEX_0_MATCHER = MethodMatchers.create()
.ofSubTypes(ORG_ASSERTJ_CORE_API_ABSTRACT_ASSERT)
.names("allSatisfy", "anySatisfy", "hasOnlyOneElementSatisfying", "noneSatisfy", "satisfies")
.addParametersMatcher(JAVA_UTIL_FUNCTION_CONSUMER)
.addParametersMatcher(ORG_ASSERTJ_CORE_API_THROWING_CONSUMER)
.addParametersMatcher(ORG_ASSERTJ_CORE_API_THROWING_CONSUMER_ARRAY)
.addParametersMatcher(JAVA_UTIL_FUNCTION_CONSUMER, "org.assertj.core.data.Index")
.build();
private static final MethodMatchers METHODS_WITH_CONSUMER_AT_INDEX_1_MATCHER = MethodMatchers.create()
.ofSubTypes(ORG_ASSERTJ_CORE_API_ABSTRACT_ASSERT)
.names("isInstanceOfSatisfying", "zipSatisfy")
.addParametersMatcher(arguments -> arguments.size() > 1 && (arguments.get(1).is(JAVA_UTIL_FUNCTION_CONSUMER) || arguments.get(1).is("java.util.function.BiConsumer")))
.build();
private static final MethodMatchers SATISFIES_ANY_OF_MATCHER = MethodMatchers.create()
.ofSubTypes(ORG_ASSERTJ_CORE_API_ABSTRACT_ASSERT)
.names("satisfiesAnyOf")
.withAnyParameters()
.build();
private final Map assertionInLocalMethod = new HashMap<>();
@Override
public List nodesToVisit() {
return singletonList(Tree.Kind.METHOD_INVOCATION);
}
@Override
public void visitNode(Tree tree) {
MethodInvocationTree invocation = (MethodInvocationTree) tree;
if (METHODS_WITH_CONSUMER_AT_INDEX_0_MATCHER.matches(invocation)) {
checkAssertions(invocation, singletonList(invocation.arguments().get(0)));
} else if (METHODS_WITH_CONSUMER_AT_INDEX_1_MATCHER.matches(invocation)) {
checkAssertions(invocation, singletonList(invocation.arguments().get(1)));
} else if (SATISFIES_ANY_OF_MATCHER.matches(invocation)) {
checkAssertions(invocation, invocation.arguments());
}
}
@Override
public void leaveFile(JavaFileScannerContext context) {
assertionInLocalMethod.clear();
super.leaveFile(context);
}
private void checkAssertions(MethodInvocationTree invocation, List argumentsToCheck) {
List argumentsMissingAssertion = argumentsToCheck.stream()
.filter(argument -> !hasAssertion(argument))
.map(argument -> new Location("Argument missing assertion", argument))
.toList();
if (!argumentsMissingAssertion.isEmpty()) {
IdentifierTree methodName = ExpressionUtils.methodName(invocation);
reportIssue(
methodName,
"Rework this assertion to assert something inside the Consumer argument.",
argumentsMissingAssertion,
null);
}
}
private boolean hasAssertion(@Nullable ExpressionTree expressionTree) {
// if the expression to check cannot be resolved, we assume it has assertions to avoid FP
if (expressionTree == null) {
return true;
}
if (expressionTree.is(Tree.Kind.IDENTIFIER)) {
Tree argumentDeclaration = ((IdentifierTree) expressionTree).symbol().declaration();
return argumentDeclaration instanceof VariableTree variableTree && hasAssertion(variableTree.initializer());
} else {
AssertionVisitor assertionVisitor = new AssertionVisitor();
expressionTree.accept(assertionVisitor);
return assertionVisitor.hasAssertion();
}
}
private class AssertionVisitor extends AbstractAssertionVisitor {
@Override
protected boolean isAssertion(Symbol methodSymbol) {
if (!assertionInLocalMethod.containsKey(methodSymbol)) {
assertionInLocalMethod.put(methodSymbol, false);
Tree declaration = methodSymbol.declaration();
if (declaration != null) {
AssertionVisitor assertionVisitor = new AssertionVisitor();
declaration.accept(assertionVisitor);
assertionInLocalMethod.put(methodSymbol, assertionVisitor.hasAssertion());
}
}
return assertionInLocalMethod.get(methodSymbol);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy