SONARJAVA-6273 S3706: "stream" should not be used for Collection "forEach" calls#5581
SONARJAVA-6273 S3706: "stream" should not be used for Collection "forEach" calls#5581NoemieBenard wants to merge 18 commits intomasterfrom
Conversation
SummaryThis PR implements SonarQube rule S3706: "stream" should not be used for Collection "forEach" calls. The rule detects when code unnecessarily chains Changes include:
What reviewers should knowWhere to focus:
|
| list.stream().forEach(e -> System.out.println("Element: " + e)); // Noncompliant | ||
| } | ||
|
|
||
| void necessaryForEachOnParallelStream(Set<String> set) { |
There was a problem hiding this comment.
The noncompliant cases cover Collection, Set, and List, but there is no test for a stream().forEach() call where .stream() returns a custom Stream produced by an overriding implementation. More importantly, there is no test using a concrete ArrayList or other class that resolves through the full ofSubTypes hierarchy.
More critically: Stream.forEach() carries a weaker contract than Iterable.forEach(). Per the JDK spec, Stream.forEach() is "explicitly nondeterministic" even on sequential streams, while Collection.forEach() (via Iterable) iterates in encounter order. Code that relies on stream().forEach() for its documented non-determinism guarantee — even if that's unusual — would have its semantics silently changed. Add a test case comment explaining this known limitation, or document it explicitly in the rule description.
- Mark as noise
| private static final MethodMatchers STREAM_METHOD = MethodMatchers.create() | ||
| .ofSubTypes("java.util.Collection") | ||
| .names("stream") | ||
| .withAnyParameters() |
There was a problem hiding this comment.
Since stream() doesn't take any parameters, could we make this match stricter?
| } | ||
|
|
||
| private void checkUnnecessaryForEach(MethodInvocationTree mit) { | ||
| ExpressionTree methodSelect = mit.methodSelect(); |
There was a problem hiding this comment.
You can try something along these lines:
if (mitForEach.methodSelect() instanceof MemberSelectExpressionTree mset
&& mset.expression() instanceof MethodInvocationTree mitStream
&& STREAM_METHOD.matches(mitStream)) {
reportIssue(mitStream, "Replace unnecessary call to .stream().forEach() by .forEach()");
}
}
Also, we can call strings streamMit and forEachMit or something like this.
| StreamForeachCheck check = new StreamForeachCheck(); | ||
| CheckVerifier.newVerifier() | ||
| .onFile(mainCodeSourcesPath("checks/StreamForeachCheck.java")) | ||
| .withoutSemantic() |
There was a problem hiding this comment.
I meant, we should have two tests, one with semantic, one without.
| void unnecessaryStreamForEach(Collection<Integer> collection) { | ||
| collection.stream().forEach(System.out::println); // Noncompliant | ||
| collection.stream().forEach(System.out::println); // Noncompliant {{Replace unnecessary call to .stream().forEach() by .forEach()}} | ||
| //^^^^^^^^^^^^^^^^^^^ |
There was a problem hiding this comment.
If we are pointing at collection.stream() then maybe the message should be Remove unnecessary call to .stream() ? It's possible to tweak the location of the issue quite freely with some adjustments to report issue.
Btw, I told you that quckfixes are difficult. This is generally true, but in this case it's not too difficult, so maybe we could do it in a follow-up PR later.
|
There was a problem hiding this comment.
LGTM! ✅
The previously flagged issues around test coverage (concrete Collection subtypes, ArrayList) and the missing documentation about the semantic difference between Stream.forEach() (nondeterministic) and Collection.forEach() (encounter-order) are still unresolved — neither the test file nor the rule description were updated in this commit.




Implementation of rule S3706.