diff --git a/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/HasTypeArguments.java b/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/HasTypeArguments.java new file mode 100644 index 0000000000..d9a1eab577 --- /dev/null +++ b/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/HasTypeArguments.java @@ -0,0 +1,39 @@ +package tech.picnic.errorprone.refaster.matchers; + +import com.google.errorprone.VisitorState; +import com.google.errorprone.matchers.Matcher; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.NewClassTree; +import com.sun.source.tree.ParameterizedTypeTree; +import com.sun.source.tree.Tree.Kind; + +/** A matcher of expressions with type arguments. */ +public final class HasTypeArguments implements Matcher { + private static final long serialVersionUID = 1L; + + /** Instantiates a new {@link HasTypeArguments} instance. */ + public HasTypeArguments() {} + + @Override + public boolean matches(ExpressionTree tree, VisitorState state) { + switch (tree.getKind()) { + case METHOD_INVOCATION: + return !((MethodInvocationTree) tree).getTypeArguments().isEmpty(); + case NEW_CLASS: + NewClassTree classTree = (NewClassTree) tree; + if (!classTree.getTypeArguments().isEmpty()) { + return true; + } + + ExpressionTree identifier = classTree.getIdentifier(); + if (identifier.getKind() != Kind.PARAMETERIZED_TYPE) { + return false; + } + + return !((ParameterizedTypeTree) identifier).getTypeArguments().isEmpty(); + default: + return false; + } + } +} diff --git a/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/HasTypeArgumentsTest.java b/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/HasTypeArgumentsTest.java new file mode 100644 index 0000000000..00910a7cd8 --- /dev/null +++ b/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/HasTypeArgumentsTest.java @@ -0,0 +1,79 @@ +package tech.picnic.errorprone.refaster.matchers; + +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; + +import com.google.errorprone.BugPattern; +import com.google.errorprone.CompilationTestHelper; +import com.google.errorprone.bugpatterns.BugChecker; +import org.junit.jupiter.api.Test; + +final class HasTypeArgumentsTest { + @Test + void matches() { + CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass()) + .addSourceLines( + "A.java", + "import com.google.common.collect.ImmutableSet;", + "import java.util.ArrayList;", + "import java.util.List;", + "", + "class A {", + " Object negative1() {", + " return toString();", + " }", + "", + " Object negative2() {", + " return new Object();", + " }", + "", + " List negative3() {", + " return new ArrayList<>();", + " }", + "", + " Foo negative4() {", + " return new Foo(\"foo\");", + " }", + "", + " ImmutableSet positive1() {", + " // BUG: Diagnostic contains:", + " return ImmutableSet.builder().build();", + " }", + "", + " ImmutableSet positive2() {", + " // BUG: Diagnostic contains:", + " return new ImmutableSet.Builder().build();", + " }", + "", + " Foo positive3() {", + " // BUG: Diagnostic contains:", + " return new >Foo(List.of());", + " }", + "", + " public static class Foo {", + " private final Object value;", + "", + " public Foo(Object value) {", + " this.value = value;", + " }", + "", + " public > Foo(E value) {", + " this.value = value;", + " }", + " }", + "}") + .doTest(); + } + + /** A {@link BugChecker} that simply delegates to {@link HasTypeArguments}. */ + @BugPattern(summary = "Flags expressions matched by `HasTypeArguments`", severity = ERROR) + public static final class MatcherTestChecker extends AbstractMatcherTestChecker { + private static final long serialVersionUID = 1L; + + // XXX: This is a false positive reported by Checkstyle. See + // https://github.com/checkstyle/checkstyle/issues2/10161#issuecomment-1242732120. + @SuppressWarnings("RedundantModifier") + public MatcherTestChecker() { + super(new HasTypeArguments()); + } + } +}