All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.tests.AssertJTestForEmptinessCheck Maven / Gradle / Ivy

There is a newer version: 8.10.0.38194
Show newest version
/*
 * 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.Collections;
import java.util.Optional;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.MethodTreeUtils;
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.MemberSelectExpressionTree;
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.TypeCastTree;

@Rule(key = "S5841")
public class AssertJTestForEmptinessCheck extends AbstractMethodDetection {

  private static final MethodMatchers ASSERTJ_NOT_TESTING_SIZE = MethodMatchers.create()
    .ofSubTypes("org.assertj.core.api.AbstractAssert")
    .name(name ->
      "as".equals(name) || "describedAs".equals(name) || "withFailMessage".equals(name) || "overridingErrorMessage".equals(name)
        || "isNotNull".equals(name) || "asList".equals(name) || name.contains("InstanceOf")|| name.startsWith("using")
        || name.startsWith("extracting") || name.startsWith("filtered") || name.startsWith("doesNotContain") || name.startsWith("all")
    ).withAnyParameters()
    .build();

  private static final MethodMatchers ASSERT_THAT_MATCHER = MethodMatchers.create()
    .ofSubTypes("org.assertj.core.api.Assertions", "org.assertj.core.api.AssertionsForInterfaceTypes", "org.assertj.core.api.AssertionsForClassTypes")
    .names("assertThat", "assertThatObject").addParametersMatcher(MethodMatchers.ANY).build();

  @Override
  protected MethodMatchers getMethodInvocationMatchers() {
    return MethodMatchers.or(
      MethodMatchers.create()
        .ofSubTypes("org.assertj.core.api.AbstractIterableAssert")
        .names("allMatch", "allSatisfy", "doesNotContainSequence", "doesNotContainSubsequence", "doesNotContainAnyElementsOf")
        .withAnyParameters()
        .build(),
      MethodMatchers.create()
        .ofSubTypes("org.assertj.core.api.AbstractIterableAssert")
        .names("doesNotContain")
        .addParametersMatcher(MethodMatchers.ANY)
        .build()
    );
  }

  @Override
  protected void onMethodInvocationFound(MethodInvocationTree mit) {
    if (consecutiveInvocationTestSize(mit)) {
      return;
    }

    getSubjectArgumentNotTestedForSize(mit)
      .filter(AssertJTestForEmptinessCheck::isNotUsedSomewhereElse)
      .ifPresent(arg -> reportIssueWithSecondary(mit, arg));
  }

  private static boolean consecutiveInvocationTestSize(MethodInvocationTree mit) {
    Optional consecutiveMethodInvocation = MethodTreeUtils.consecutiveMethodInvocation(mit);
    if (consecutiveMethodInvocation.isPresent()) {
      MethodInvocationTree consecutiveInvocation = consecutiveMethodInvocation.get();
      if (ASSERTJ_NOT_TESTING_SIZE.matches(consecutiveInvocation)) {
        return consecutiveInvocationTestSize(consecutiveInvocation);
      } else {
        // To avoid FP, only every others methods not explicitly listed as not testing size, will be considered as testing size
        return true;
      }
    }
    return false;
  }

  private static Optional getSubjectArgumentNotTestedForSize(MethodInvocationTree mit) {
    ExpressionTree methodSelect = mit.methodSelect();
    if (methodSelect.is(Tree.Kind.MEMBER_SELECT)) {
      ExpressionTree memberSelectExpression = ((MemberSelectExpressionTree) methodSelect).expression();
      if (memberSelectExpression.is(Tree.Kind.METHOD_INVOCATION)) {
        MethodInvocationTree childMit = (MethodInvocationTree) memberSelectExpression;
        if (ASSERT_THAT_MATCHER.matches(childMit)) {
          return Optional.of(childMit.arguments().get(0));
        } else if (ASSERTJ_NOT_TESTING_SIZE.matches(childMit)) {
          return getSubjectArgumentNotTestedForSize(childMit);
        }
      }
    }
    return Optional.empty();
  }

  private static boolean isNotUsedSomewhereElse(ExpressionTree tree) {
    if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
      return isNotUsedSomewhereElse(((MethodInvocationTree) tree).methodSelect());
    } else if (tree.is(Tree.Kind.MEMBER_SELECT)) {
      MemberSelectExpressionTree memberSelectExpressionTree = (MemberSelectExpressionTree) tree;
      if (!ExpressionUtils.isSelectOnThisOrSuper(memberSelectExpressionTree)) {
        return isNotUsedSomewhereElse(((MemberSelectExpressionTree) tree).expression());
      }
    } else if (tree.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) {
      return isNotUsedSomewhereElse(((ParenthesizedTree) tree).expression());
    } else if (tree.is(Tree.Kind.TYPE_CAST)) {
      return isNotUsedSomewhereElse(((TypeCastTree) tree).expression());
    } else if (tree.is(Tree.Kind.IDENTIFIER)) {
      return ((IdentifierTree) tree).symbol().usages().size() == 1;
    }
    return false;
  }

  private void reportIssueWithSecondary(MethodInvocationTree mit, ExpressionTree argument) {
    reportIssue(ExpressionUtils.methodName(mit),
      "Test the emptiness of the list before calling this assertion predicate.",
      Collections.singletonList(new JavaFileScannerContext.Location("", argument)),
      null);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy