diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishers.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishers.java new file mode 100644 index 00000000000..768206cc5dd --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishers.java @@ -0,0 +1,58 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.CUSTOM; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE; +import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; +import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic; +import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf; +import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type; +import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound; + +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.MethodInvocationTreeMatcher; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.suppliers.Supplier; +import com.google.errorprone.suppliers.Suppliers; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.tools.javac.code.Type; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +/** + * A {@link BugChecker} that flags nesting of {@link Publisher Publishers} inside {@link Mono + * Monos}. + */ +@AutoService(BugChecker.class) +@BugPattern( + summary = + "Avoid nesting `Publisher`s inside `Mono`s; the resultant code is hard to reason about", + link = BUG_PATTERNS_BASE_URL + "MonoOfPublishers", + linkType = CUSTOM, + severity = WARNING, + tags = FRAGILE_CODE) +public final class MonoOfPublishers extends BugChecker implements MethodInvocationTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Supplier MONO = + Suppliers.typeFromString("reactor.core.publisher.Mono"); + private static final Supplier MONO_OF_PUBLISHERS = + VisitorState.memoize( + generic(MONO, subOf(generic(type("org.reactivestreams.Publisher"), unbound())))); + + /** Instantiates a new {@link MonoOfPublishers} instance. */ + public MonoOfPublishers() {} + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + Type type = MONO_OF_PUBLISHERS.get(state); + if (type == null || !state.getTypes().isSubtype(ASTHelpers.getType(tree), type)) { + return Description.NO_MATCH; + } + + return describeMatch(tree); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java index f7c9d15eaa2..ee5aa21dcf9 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java @@ -782,6 +782,7 @@ Flux after(Flux flux) { /** Prefer {@link Mono#flatMap(Function)} over more contrived alternatives. */ static final class MonoFlatMap { @BeforeTemplate + @SuppressWarnings("MonoOfPublishers") Mono before(Mono mono, Function> function) { return mono.map(function).flatMap(identity()); } @@ -795,6 +796,7 @@ Mono after(Mono mono, Function> fun /** Prefer {@link Mono#flatMapMany(Function)} over more contrived alternatives. */ static final class MonoFlatMapMany { @BeforeTemplate + @SuppressWarnings("MonoOfPublishers") Flux before(Mono mono, Function> function) { return mono.map(function).flatMapMany(identity()); } diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishersTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishersTest.java new file mode 100644 index 00000000000..1963c4aab60 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/MonoOfPublishersTest.java @@ -0,0 +1,45 @@ +package tech.picnic.errorprone.bugpatterns; + +import com.google.errorprone.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +final class MonoOfPublishersTest { + @Test + void identification() { + CompilationTestHelper.newInstance(MonoOfPublishers.class, getClass()) + .addSourceLines( + "A.java", + "import reactor.core.publisher.Flux;", + "import reactor.core.publisher.Mono;", + "", + "class A {", + " void m() {", + " Mono.empty();", + " Mono.just(1);", + " // BUG: Diagnostic contains:", + " Mono.just(Mono.empty());", + " // BUG: Diagnostic contains:", + " Mono.just(Mono.just(1));", + " // BUG: Diagnostic contains:", + " Mono.just(Flux.empty());", + " // BUG: Diagnostic contains:", + " Mono.just(Flux.just(1));", + " // BUG: Diagnostic contains:", + " Mono.just(1).map(Mono::just);", + " // BUG: Diagnostic contains:", + " Mono.just(1).map(Flux::just);", + "", + " Mono.justOrEmpty(null);", + " // BUG: Diagnostic contains:", + " Mono.justOrEmpty(Mono.just(1));", + " // BUG: Diagnostic contains:", + " Mono.justOrEmpty(Flux.just(1));", + " // BUG: Diagnostic contains:", + " Mono.justOrEmpty(1).map(Mono::just);", + " // BUG: Diagnostic contains:", + " Mono.justOrEmpty(1).map(Flux::just);", + " }", + "}") + .doTest(); + } +}