diff --git a/error-prone-contrib/pom.xml b/error-prone-contrib/pom.xml index a49552a2d0e..a0dd1e7ada6 100644 --- a/error-prone-contrib/pom.xml +++ b/error-prone-contrib/pom.xml @@ -141,6 +141,10 @@ hamcrest test + + org.immutables + value + org.junit.jupiter junit-jupiter-api diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheck.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheck.java new file mode 100644 index 00000000000..df52fabe395 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheck.java @@ -0,0 +1,69 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.NONE; +import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; +import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR; +import static com.google.errorprone.matchers.Matchers.hasAnnotation; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import org.immutables.value.Value; + +/** + * A {@link BugChecker} which flags methods with return type {@link + * com.google.common.collect.ImmutableSortedSet} within an {@code @Value.Immutable} or + * {@code @Value.Modifiable} class that lack either a default implementation or + * {@code @Value.NaturalOrder}. + */ +@AutoService(BugChecker.class) +@BugPattern( + name = "MissingImmutableSortedSetDefault", + summary = + "Properties of type `ImmutableSortedSet` within an @Value.Immutable or @Value.Modifiable class " + + "should provide a default value or specify the comparator.", + linkType = NONE, + severity = ERROR, + tags = LIKELY_ERROR) +public final class MissingImmutableSortedSetDefaultCheck extends BugChecker + implements MethodTreeMatcher { + private static final long serialVersionUID = 1L; + private static final String VALUE_NATURAL_ORDER_ANNOTATION = + "org.immutables.value.Value.NaturalOrder"; + private static final Matcher HAS_NATURAL_ORDER = + hasAnnotation(VALUE_NATURAL_ORDER_ANNOTATION); + + @Override + public Description matchMethod(MethodTree tree, VisitorState state) { + // is not within immutable or modifiable class -> no match + if (tree.getClass().isAnnotationPresent(org.immutables.value.Value.Immutable.class) + || tree.getClass().isAnnotationPresent(Value.Modifiable.class)) { + return Description.NO_MATCH; + } + + // has implementation -> no match + if (tree.getDefaultValue() != null) { + return Description.NO_MATCH; + } + + // is annotated with @Value.NaturalOrder -> no match + if (HAS_NATURAL_ORDER.matches(tree, state)) { + return Description.NO_MATCH; + } + + // The ImmutableSortedSet has no empty default -> add the `@Value.NaturalOrder` annotation. + return describeMatch( + tree, + SuggestedFix.builder() + .addStaticImport(VALUE_NATURAL_ORDER_ANNOTATION) + .prefixWith(tree, "@Value.NaturalOrder ") + .build()); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheckTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheckTest.java new file mode 100644 index 00000000000..c612088e016 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MissingImmutableSortedSetDefaultCheckTest.java @@ -0,0 +1,223 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.common.base.Predicates.containsPattern; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; +import com.google.errorprone.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +public final class MissingImmutableSortedSetDefaultCheckTest { + private final CompilationTestHelper compilationTestHelper = + CompilationTestHelper.newInstance(MissingImmutableSortedSetDefaultCheck.class, getClass()) + .expectErrorMessage( + "X", + containsPattern( + "Properties of type `ImmutableSortedSet` within an @Value.Immutable or @Value.Modifiable class should provide a default value or specify the comparator.")); + private final BugCheckerRefactoringTestHelper refactoringTestHelper = + BugCheckerRefactoringTestHelper.newInstance( + MissingImmutableSortedSetDefaultCheck.class, getClass()); + + @Test + void identification() { + compilationTestHelper + .addSourceLines( + "A.java", + "import org.immutables.value.Value;", + "import com.google.common.collect.ImmutableSet;", + "import com.google.common.collect.ImmutableSortedSet;", + "", + "@Value.Immutable", + "interface A {", + " // BUG: Diagnostic matches: X", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "interface B {", + " // BUG: Diagnostic matches: X", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Immutable", + "abstract class C {", + " // BUG: Diagnostic matches: X", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "abstract class D {", + " // BUG: Diagnostic matches: X", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}") + .doTest(); + } + + @Test + void replacement() { + refactoringTestHelper + .addInputLines( + "A.java", + "import org.immutables.value.Value;", + "import com.google.common.collect.ImmutableSet;", + "import com.google.common.collect.ImmutableSortedSet;", + "", + "@Value.Immutable", + "interface A {", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "interface B {", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Immutable", + "abstract class C {", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "abstract class D {", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}") + .addOutputLines( + "A.java", + "import org.immutables.value.Value;", + "import com.google.common.collect.ImmutableSet;", + "import com.google.common.collect.ImmutableSortedSet;", + "", + "@Value.Immutable", + "interface A {", + " @Value.NaturalOrder", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "interface B {", + " @Value.NaturalOrder", + " ImmutableSortedSet sortedSet();", + " default ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " default ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Immutable", + "abstract class C {", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}", + "", + "@Value.Modifiable", + "abstract class D {", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet sortedSet();", + " ImmutableSortedSet defaultSortedSet() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.Default", + " ImmutableSortedSet defaultSortedSet2() {", + " return ImmutableSortedSet.of();", + " }", + " @Value.NaturalOrder", + " abstract ImmutableSortedSet defaultSortedSet3();", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/pom.xml b/pom.xml index d7134064da8..561e4961dd3 100644 --- a/pom.xml +++ b/pom.xml @@ -364,6 +364,11 @@ hamcrest-library ${version.hamcrest} + + org.immutables + value + 2.8.2 + org.junit junit-bom