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

org.sonar.java.checks.tests.AssertJChainSimplificationCheck 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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.MethodTreeUtils;
import org.sonar.java.checks.helpers.QuickFixHelper;
import org.sonar.java.checks.methods.AbstractMethodDetection;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.reporting.InternalJavaIssueBuilder;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;

import static org.sonar.java.checks.tests.AssertJChainSimplificationIndex.CONTEXT_FREE_SIMPLIFIERS;
import static org.sonar.java.checks.tests.AssertJChainSimplificationIndex.SIMPLIFIERS_WITH_CONTEXT;

@Rule(key = "S5838")
public class AssertJChainSimplificationCheck extends AbstractMethodDetection {
  private static final String ISSUE_MESSAGE_FORMAT_STRING = "Use %s instead.";

  private static final MethodMatchers ASSERTION_MESSAGE_METHODS = MethodMatchers.create()
    .ofSubTypes("org.assertj.core.api.AbstractAssert")
    .names("as", "describedAs", "withFailMessage", "overridingErrorMessage").withAnyParameters().build();

  private static final MethodMatchers ASSERTIONS_SUBJECT_METHODS = MethodMatchers.create().ofTypes(
    "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 ASSERTIONS_SUBJECT_METHODS;
  }

  @Override
  protected void onMethodInvocationFound(MethodInvocationTree subjectMit) {
    List predicates = new ArrayList<>();
    Optional nextPredicateOpt = MethodTreeUtils.consecutiveMethodInvocation(subjectMit);

    while (nextPredicateOpt.isPresent()) {
      MethodInvocationTree nextPredicate = nextPredicateOpt.get();
      if (!ASSERTION_MESSAGE_METHODS.matches(nextPredicate)) {
        predicates.add(nextPredicate);
      }
      nextPredicateOpt = MethodTreeUtils.consecutiveMethodInvocation(nextPredicate);
    }

    boolean wasIssueRaised = checkPredicatesForSimplification(
      predicates, CONTEXT_FREE_SIMPLIFIERS, SimplifierWithoutContext::simplify,
      (predicate, simplification) -> createIssueBuilder(predicate, simplification).report()
    );

    // We do not continue when we have already raised an issue to avoid potentially conflicting issue reports. If we
    // have more than one predicate we also avoid continuing to avoid FP on cases such as:
    // assertThat(Integer.valueOf(1).compareTo(2)).isGreaterThan(1).isLessThan(10)
    // We also want to ignore all assertion chains that contain methods besides predicates and messages, such as those
    // that change the assertion context, as that level of complexity is not handled by this rule. The extraction
    // function is such an example:
    // assertThat(frodo).extracting("name", as(InstanceOfAssertFactories.STRING)).startsWith("Fro");
    if (wasIssueRaised || predicates.size() > 1) {
      return;
    }

    checkPredicatesForSimplification(
      predicates, SIMPLIFIERS_WITH_CONTEXT, (simplifier, predicate) -> simplifier.simplify(subjectMit, predicate),
      (predicate, simplification) -> createIssueBuilder(predicate, simplification)
        .withSecondaries(new JavaFileScannerContext.Location("This can be simplified", subjectMit.arguments().get(0)))
        .report()
    );
  }

  private InternalJavaIssueBuilder createIssueBuilder(MethodInvocationTree predicate, AssertJChainSimplificationIndex.Simplification simplification) {
    InternalJavaIssueBuilder builder = QuickFixHelper.newIssue(context)
      .forRule(this)
      .onTree(ExpressionUtils.methodName(predicate))
      .withMessage(ISSUE_MESSAGE_FORMAT_STRING, simplification.getReplacement());
    simplification.getQuickFix().ifPresent(builder::withQuickFixes);
    return builder;
  }

  /**
   * @return {@code true} when an issue was reported, {@code false} otherwise.
   */
  private static  boolean checkPredicatesForSimplification(
    List predicates,
    Map> simplifiers,
    BiFunction> simplificationMethod,
    BiConsumer reportingMethod) {

    AssertJChainSimplificationHelper.BooleanFlag issueRaised = new AssertJChainSimplificationHelper.BooleanFlag();
    predicates.forEach(predicate -> {
      String predicateName = ExpressionUtils.methodName(predicate).name();
      if (simplifiers.containsKey(predicateName)) {
        simplifiers.get(predicateName).stream()
          .map(simplifier -> simplificationMethod.apply(simplifier, predicate))
          .filter(Optional::isPresent)
          .map(Optional::get)
          .findFirst()
          .ifPresent(replacement -> {
            reportingMethod.accept(predicate, replacement);
            issueRaised.setTrue();
          });
      }
    });
    return issueRaised.value();
  }

  interface SimplifierWithoutContext {
    Optional simplify(MethodInvocationTree predicate);
  }

  interface SimplifierWithContext {
    Optional simplify(MethodInvocationTree subject, MethodInvocationTree predicate);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy