Skip to content

Commit

Permalink
Create JUnit matchers as preparation for additional JUnit BugCheckers
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-picnic committed Nov 4, 2022
1 parent 42e632e commit 1b41f20
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package tech.picnic.errorprone.bugpatterns.util;

import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import javax.lang.model.element.Name;

/** A set of helper methods for working with the AST. */
public final class MoreASTHelpers {
private MoreASTHelpers() {}

/**
* Finds methods with the given name in the given class.
*
* @param enclosingClass The class to search in.
* @param methodName The method name to search for.
* @return The method trees of the methods with the given name in the class.
*/
public static ImmutableList<MethodTree> findMethods(ClassTree enclosingClass, String methodName) {
return enclosingClass.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(method -> method.getName().contentEquals(methodName))
.collect(toImmutableList());
}

static boolean isMethodInEnclosingClass(String methodName, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.map(MethodTree::getName)
.map(Name::toString)
.anyMatch(methodName::equals);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package tech.picnic.errorprone.bugpatterns.util;

import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;

import com.google.common.collect.Iterables;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
import javax.lang.model.type.TypeKind;

/** A set of JUnit-specific helpers for working with the AST. */
public final class MoreJUnitMatchers {
/** Matches JUnit test methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));

/** Matches JUnit setup and teardown methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));

/**
* Matches methods which have a {@link org.junit.jupiter.params.provider.MethodSource} annotation.
*/
public static final Matcher<MethodTree> HAS_METHOD_SOURCE =
allOf(annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource")));

private MoreJUnitMatchers() {}

/**
* Extracts the name of the JUnit factory method from a {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* @param methodSourceAnnotation The {@link org.junit.jupiter.params.provider.MethodSource}
* annotation to extract a method name from.
* @return The name of the factory methods referred to in the annotation if there is only one, or
* {@link Optional#empty()} if there is more than one.
*/
public static Optional<String> extractSingleFactoryMethodName(
AnnotationTree methodSourceAnnotation) {
ExpressionTree attributeExpression =
((AssignmentTree) Iterables.getOnlyElement(methodSourceAnnotation.getArguments()))
.getExpression();
Type attributeType = ASTHelpers.getType(attributeExpression);
return attributeType.getKind() == TypeKind.ARRAY
? Optional.empty()
: Optional.of(attributeType.stringValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package tech.picnic.errorprone.bugpatterns.util;

import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;

/** A collection of methods to enhance the use of {@link Matcher}s. */
public final class MoreMatchers {
private MoreMatchers() {}

/**
* Determines whether an expression has a meta annotation of the given class name. This includes
* annotations inherited from superclasses due to {@link java.lang.annotation.Inherited}.
*
* @param <T> The type of the expression tree.
* @param annotationClass The binary class name of the annotation (e.g. "
* org.jspecify.nullness.Nullable", or "some.package.OuterClassName$InnerClassName")
* @return A {@link Matcher} that matches expressions with the specified meta annotation.
*/
public static <T extends Tree> Matcher<T> hasMetaAnnotation(String annotationClass) {
TypePredicate typePredicate = hasAnnotation(annotationClass);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}

// XXX: Consider moving to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

0 comments on commit 1b41f20

Please sign in to comment.