Skip to content

Conversation

@mohamedsamehsalah
Copy link
Contributor

Suggested commit message

Introduce `EmptyReactivePublisher` bug checker

" Mono.just(16).subscribe(null, t -> {});",
"",
" // BUG: Diagnostic contains:",
" Mono.just(17).then().subscribe(i -> {});",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the statement that inspired this bug checker; this consumer is never called.

@sonarqubecloud
Copy link

@github-actions
Copy link

  • Surviving mutants in this change: 8
  • Killed mutants in this change: 14
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EmptyReactivePublisher 8 14

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie modified the milestones: 0.21.0, 0.22.0 Mar 19, 2025
@Stephan202 Stephan202 modified the milestones: 0.22.0, 0.23.0, 0.24.0 Apr 14, 2025
@rickie rickie modified the milestones: 0.24.0, 0.25.0 Jul 21, 2025
@rickie rickie modified the milestones: 0.25.0, 0.26.0 Sep 22, 2025
@Stephan202 Stephan202 modified the milestones: 0.26.0, 0.28.0 Oct 26, 2025
@rickie rickie force-pushed the mohamedsamehsalah/empty-reactive-publisher branch from e8a8e37 to a83f846 Compare December 3, 2025 14:19
@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Suggested commit message:

Introduce `EmptyReactivePublisher` bug checker (#1560)

Add `EmptyReactivePublisher` to warn on vacuous Reactor operators
invoked on empty `Mono`/`Flux` instances or empty subscriptions.

Comment on lines 128 to 130
String.format(
"Operator `%s` on an empty `Mono`s is a no-op",
ASTHelpers.getSymbol(tree).getSimpleName()))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Error Prone] reported by reviewdog 🐶

Suggested change
String.format(
"Operator `%s` on an empty `Mono`s is a no-op",
ASTHelpers.getSymbol(tree).getSimpleName()))
"Operator `%s` on an empty `Mono`s is a no-op"
.formatted(ASTHelpers.getSymbol(tree).getSimpleName()))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will apply.

@github-actions
Copy link

github-actions bot commented Dec 3, 2025

  • Surviving mutants in this change: 8
  • Killed mutants in this change: 18
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EmptyReactivePublisher 8 18

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushing a commit with many small suggestions. PTAL and feel free to tweak 😄.

Very elegant PR. The testing makes refactoring easy 🚀.


/**
* A {@link BugChecker} that flags {@link Publisher} operations that are known to be vacuous, given
* that they are invoked on a {@link Mono} or {@link Flux} that do not emit next signals.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* that they are invoked on a {@link Mono} or {@link Flux} that do not emit next signals.
* that they are invoked on a {@link Mono} or {@link Flux} that does not emit next signals.

Right?

public final class EmptyReactivePublisher extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change


private static final Matcher<ExpressionTree> SUBSCRIBE =
instanceMethod()
.onDescendantOf(Suppliers.typeFromString("org.reactivestreams.Publisher"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.onDescendantOf(Suppliers.typeFromString("org.reactivestreams.Publisher"))
.onDescendantOf(type("org.reactivestreams.Publisher"))

Also works :) ?

Suppliers.typeFromString("reactor.core.publisher.Flux");
private static final Matcher<Tree> CONSUMER =
isSubtypeOf(type(Consumer.class.getCanonicalName()));
private static final Supplier<Type> VOID = type(Void.class.getCanonicalName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one in Error Prone itself we can use :)


if (SUBSCRIBE.matches(tree, state)) {
// First argument passed to `#subscribe` is always an on next signal `Consumer`.
ExpressionTree firstArgument = Iterables.getFirst(tree.getArguments(), null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps simply .getFirst() on the collection?

if (EMPTY_MONO.matches(receiver, state) && VACUOUS_EMPTY_MONO_OPERATORS.matches(tree, state)) {
return buildDescription(tree)
.setMessage(
"Operator `%s` on an empty `Mono`s is a no-op"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can extract this into a method :)

.build();
}

if (EMPTY_MONO.matches(receiver, state) && VACUOUS_EMPTY_MONO_OPERATORS.matches(tree, state)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can omit this first part of the if-statement

isSubtypeOf(type(Consumer.class.getCanonicalName()));
private static final Supplier<Type> VOID = type(Void.class.getCanonicalName());

private static final Matcher<ExpressionTree> EMPTY_FLUX =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably combine this one and the next

Suppliers.typeFromString("reactor.core.publisher.Flux");
private static final Matcher<Tree> CONSUMER =
isSubtypeOf(type(Consumer.class.getCanonicalName()));
private static final Supplier<Type> VOID = type(Void.class.getCanonicalName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one provided by EP :D

" Mono.just(6).handle((i, j) -> {});",
" Mono.just(7).map(i -> i);",
"",
" // BUG: Diagnostic contains: doOnNext",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's expand the message to be more accurate in testing :)

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

  • Surviving mutants in this change: 5
  • Killed mutants in this change: 17
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EmptyReactivePublisher 5 17

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

  • Surviving mutants in this change: 5
  • Killed mutants in this change: 17
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EmptyReactivePublisher 5 17

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 5, 2025

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

  • Surviving mutants in this change: 1
  • Killed mutants in this change: 21
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EmptyReactivePublisher 1 21

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants