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

com.google.errorprone.bugpatterns.AssertThrowsUtils Maven / Gradle / Ivy

There is a newer version: 2.30.0
Show newest version
/*
 * Copyright 2018 The Error Prone Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.bugpatterns;

import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.errorprone.util.ASTHelpers.getStartPosition;
import static java.util.stream.Collectors.joining;

import com.google.common.collect.Iterables;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TryTree;
import java.util.List;
import java.util.Optional;

/**
 * Utility methods for refactoring try-fail pattern to assertThrows, which is preferred. Used by
 * {@link TryFailRefactoring} and {@link MissingFail}.
 */
public final class AssertThrowsUtils {

  /**
   * Transforms a try-catch block in the try-fail pattern into a call to JUnit's {@code
   * assertThrows}, inserting the behavior of the {@code try} block into a lambda parameter, and
   * assigning the expected exception to a variable, if it is used within the {@code catch} block.
   * For example:
   *
   * 
   * try {
   *   foo();
   *   fail();
   * } catch (MyException expected) {
   *   assertThat(expected).isEqualTo(other);
   * }
   * 
* * becomes * *
   * {@code MyException expected = assertThrows(MyException.class, () -> foo());}
   * assertThat(expected).isEqualTo(other);
   * 
* * @param tryTree the tree representing the try-catch block to be refactored. * @param throwingStatements the list of statements in the {@code throw} clause, excluding * the fail statement. * @param state current visitor state (for source positions). * @return an {@link Optional} containing a {@link Fix} that replaces {@code tryTree} with an * equivalent {@code assertThrows}, if possible. Returns an {@code Optional.empty()} if a fix * could not be constructed for the given code (e.g. multi-catch). */ public static Optional tryFailToAssertThrows( TryTree tryTree, List throwingStatements, Optional failureMessage, VisitorState state) { List catchTrees = tryTree.getCatches(); if (catchTrees.size() != 1) { return Optional.empty(); } CatchTree catchTree = Iterables.getOnlyElement(catchTrees); SuggestedFix.Builder fix = SuggestedFix.builder(); StringBuilder fixPrefix = new StringBuilder(); String fixSuffix = ""; if (throwingStatements.isEmpty()) { return Optional.empty(); } List catchStatements = catchTree.getBlock().getStatements(); fix.addStaticImport("org.junit.Assert.assertThrows"); List resources = tryTree.getResources(); if (!resources.isEmpty()) { fixPrefix.append( resources.stream().map(state::getSourceForNode).collect(joining("\n", "try (", ") {\n"))); fixSuffix = "\n}"; } if (!catchStatements.isEmpty()) { // TODO(cushon): pick a fresh name for the variable, if necessary fixPrefix.append(String.format("%s = ", state.getSourceForNode(catchTree.getParameter()))); } fixPrefix.append( String.format( "assertThrows(%s%s.class, () -> ", failureMessage // Supplying a constant string adds little value, since a failure here always means // the same thing: the method just called wasn't expected to complete normally, but // it did. .filter(t -> ASTHelpers.constValue(t, String.class) == null) .map(t -> state.getSourceForNode(t) + ", ") .orElse(""), state.getSourceForNode(catchTree.getParameter().getType()))); boolean useExpressionLambda = throwingStatements.size() == 1 && getOnlyElement(throwingStatements).getKind() == Kind.EXPRESSION_STATEMENT; if (!useExpressionLambda) { fixPrefix.append("{"); } fix.replace( getStartPosition(tryTree), getStartPosition(throwingStatements.iterator().next()), fixPrefix.toString()); if (useExpressionLambda) { fix.postfixWith( ((ExpressionStatementTree) throwingStatements.iterator().next()).getExpression(), ")"); } else { fix.postfixWith(getLast(throwingStatements), "});"); } if (catchStatements.isEmpty()) { fix.replace( state.getEndPosition(getLast(throwingStatements)), state.getEndPosition(tryTree), fixSuffix); } else { fix.replace( /* startPos= */ state.getEndPosition(getLast(throwingStatements)), /* endPos= */ getStartPosition(catchStatements.get(0)), "\n"); fix.replace( state.getEndPosition(getLast(catchStatements)), state.getEndPosition(tryTree), fixSuffix); } return Optional.of(fix.build()); } private AssertThrowsUtils() {} }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy