Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce NonEmptyMono check #200

Merged
merged 11 commits into from
Aug 20, 2022
Merged

Introduce NonEmptyMono check #200

merged 11 commits into from
Aug 20, 2022

Conversation

cernat-catalin
Copy link
Contributor

Avoid doing Flux#collect(...) (and similar collector methods) followed by methods that check for an empty source (e.g. Mono#single(), Mono#defaultIfEmpty(...), Mono#switchIfEmpty) as collect method on a flux always emit a value (empty collection for an empty flux)

See discussion here: https://teampicnic.slack.com/archives/C0172BMUWNL/p1660557240599619

@Stephan202
Copy link
Member

Niiice! Won't have time right now to review, but love the simplicity! 🚀 Will try to review in the coming day(s) :)

@cernat-catalin cernat-catalin force-pushed the cernat-catalin/flux_collect branch 3 times, most recently from 08de097 to bc832c4 Compare August 16, 2022 21:05
@rickie rickie changed the title FluxCollect BugPattern Introduce FluxCollect BugPattern Aug 17, 2022
@Stephan202 Stephan202 force-pushed the cernat-catalin/flux_collect branch from bc832c4 to d81a6c4 Compare August 17, 2022 19:01
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Rebased and added a commit. Did not yet review the Javadoc and tests; pondering whether we can generalize even a bit more... stay tuned :)

tags = SIMPLIFICATION)
public final class FluxCollect extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> FLUX_EMPTY_CHECK =
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> FLUX_EMPTY_CHECK =
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =

instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf(
"collect", "collectList", "collectSortedList", "collectMap", "collectMultimap");
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 sort 😄

Comment on lines 58 to 62
if (tree.getMethodSelect().getKind() != MEMBER_SELECT) {
return Description.NO_MATCH;
}

ExpressionTree selectExpression = ((MemberSelectTree) tree.getMethodSelect()).getExpression();
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 instead us ASTHelpers.getReceiver(tree) :)

Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Added a commit with a proposed generalization; PTAL.

The idea is not to pick up the XXX comments in this PR: before doing so I would propose to look into a kind of "calculus" for reactive streams. That said, if this sounds interesting: let's talk! 😄

Did not yet review the tests. I suspect we should condense the test cases rather than ~quadruple the current amount. Let me know what you think :)

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.

Flushing my comments. Almost finished with a few test improvements as we often limit the amount of entries in the replacement test. Will continue tomorrow :).

Very nice check @cernat-catalin 😄:rocket:!

private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("single", "defaultIfEmpty", "switchIfEmpty");
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 sort them :).

"last",
"reduce",
"reduceWith",
"single"),
Copy link
Member

Choose a reason for hiding this comment

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

I have to ask, although I'm not 100% I understand the method, is there a reason for not including shareNext?

Copy link
Member

Choose a reason for hiding this comment

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

It's not included because that method might return a Mono that completes empty. This test passes:

Flux.empty().shareNext().as(StepVerifier::create).verifyComplete();

instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("single", "defaultIfEmpty", "switchIfEmpty");
private static final Matcher<ExpressionTree> SINGLETON_MONO =
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if SINGLETON is the most ideal name here 🤔. Will try to come up with a different name tomorrow.

Copy link
Member

Choose a reason for hiding this comment

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

NON_EMPTY_MONO would match the class name. (Would actually prefer NONEMPTY_MONO, but that doesn't quite match the type name and some other usages.)

@Stephan202 Stephan202 force-pushed the cernat-catalin/flux_collect branch from 39e4023 to 2319bfe Compare August 18, 2022 06:01
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Rebased and added a commit. Looking forward to the test tweaks 😄

instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("single", "defaultIfEmpty", "switchIfEmpty");
private static final Matcher<ExpressionTree> SINGLETON_MONO =
Copy link
Member

Choose a reason for hiding this comment

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

NON_EMPTY_MONO would match the class name. (Would actually prefer NONEMPTY_MONO, but that doesn't quite match the type name and some other usages.)

"last",
"reduce",
"reduceWith",
"single"),
Copy link
Member

Choose a reason for hiding this comment

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

It's not included because that method might return a Mono that completes empty. This test passes:

Flux.empty().shareNext().as(StepVerifier::create).verifyComplete();

@cernat-catalin
Copy link
Contributor Author

Very nice additions @Stephan202 ! It is starting to become a very encompassing BugPattern

@rickie
Copy link
Member

rickie commented Aug 18, 2022

Pushed a tweak for the replacement test. I think it is enough to test one Flux and one Mono case w.r.t. the replacement.

I was working on a proposal for the identification part as well. However, I wasn't too sure how to proceed here. It's a bit too much to add tests for all variants + the three methods we flag.
We can also leave it at this for now. WDYT?

TBH, I think it'd be okay to remove some of the entries in the identification method. WDYT?

@Stephan202
Copy link
Member

TBH, I think it'd be okay to remove some of the entries in the identification method. WDYT?

I would be in favour of that. Would I'd probably have one line for each method in NON_EMPTY_MONO, and then cycle through MONO_SIZE_CHECK. So in total that'd be NON_EMPTY_MONO.size() cases.

@cernat-catalin
Copy link
Contributor Author

Thanks both!

I've pushed a commit which rewrites the entries in the identification method. Now there are NON_EMPTY_MONO.size() entries.

However, I've removed from the NON_EMPTY_MONO set the methods defaultIfEmpty (as it produced a Flux not a Mono) and reduce (as there is an overload of this method which does not take an initial value thus the operation might indeed complete empty). There reduce method could be treated as a special case after the standard checks. The defaultIfEmpty one will need more generalization (I don't have time today or tomorrow though). WDYT?

@rickie rickie force-pushed the cernat-catalin/flux_collect branch from 57bf773 to ad67fe9 Compare August 19, 2022 13:42
@rickie
Copy link
Member

rickie commented Aug 19, 2022

as there is an overload of this method which does not take an initial value thus the operation might indeed complete empty). There reduce method could be treated as a special case after the standard checks.

Good catch, added a more specific part to the Matcher to only flag the correct one.

The defaultIfEmpty one will need more generalization (I don't have time today or tomorrow though). WDYT?

I think it's fair to leave this one out for now. This one requires some extra thought.

I added three extra test cases for the Monos as well. Also added a number to the Flux.just() occurrences. Currently the preferred way is to use Flux.just(<number>), although the existing suggestions weren't wrong. Just to be consistent I updated the tests.

@Stephan202 Stephan202 force-pushed the cernat-catalin/flux_collect branch from d858ec2 to f75bd23 Compare August 19, 2022 17:28
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

However, I've removed from the NON_EMPTY_MONO set the methods defaultIfEmpty (as it produced a Flux not a Mono)

Check, that would be a type change. I rebased and added a comment about this, because ideally we introduce a generalization which does cover this case.

Next up: reviewing the tests.

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.

Really cool check @cernat-catalin, I'm curious what the downstream impact will be! 🚀

Suggested commit message:

Introduce `NonEmptyMono` check (#200)

P.s: Notice the nice PR number 😄.

@rickie rickie added this to the 0.1.1 milestone Aug 20, 2022
@Stephan202 Stephan202 force-pushed the cernat-catalin/flux_collect branch from f75bd23 to 1c832a8 Compare August 20, 2022 08:54
Copy link
Member

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

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

Rebased and added a commit with test cases for other overloads.

Very nice check!

"import reactor.core.publisher.Mono;",
"",
"class A {",
" void m() {",
Copy link
Member

Choose a reason for hiding this comment

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

We should also add a few more expressions that aren't flagged.

@cernat-catalin
Copy link
Contributor Author

Cool stuff! Thanks @rickie and @Stephan202 for the latest additions.
I'm really happy with how this one turned out (looking forward to the generalization).

P.s: Notice the nice PR number 😄.

Nice round number!

@rickie
Copy link
Member

rickie commented Aug 20, 2022

Latest changes LGTM!

I'm really happy with how this one turned out

Me too! 😄

@Stephan202 Stephan202 merged commit b8e22ff into master Aug 20, 2022
@Stephan202 Stephan202 deleted the cernat-catalin/flux_collect branch August 20, 2022 10:47
@rickie rickie changed the title Introduce FluxCollect BugPattern Introduce NonEmptyMono check Aug 23, 2022
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.

3 participants