diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreASTHelpers.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreASTHelpers.java new file mode 100644 index 0000000000..426f545002 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreASTHelpers.java @@ -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 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); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreJUnitMatchers.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreJUnitMatchers.java new file mode 100644 index 0000000000..e65eaf625c --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreJUnitMatchers.java @@ -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 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 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 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 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()); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java new file mode 100644 index 0000000000..df07670862 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/util/MoreMatchers.java @@ -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 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 Matcher 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); + } +}