-
Notifications
You must be signed in to change notification settings - Fork 724
SONARJAVA-6673 S1602: Fix false positives #6113
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| package checks; | ||
|
|
||
| import java.lang.invoke.MethodHandle; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.stream.IntStream; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
|
|
||
| public class LambdaSingleExpressionCheckSample { | ||
| public void method() { | ||
| IntStream.range(1, 5).map(x -> x * x - 1).forEach(x -> System.out.println(x)); | ||
| IntStream.range(1, 5).map(x -> {return x * x - 1;}) // Noncompliant {{Remove useless curly braces around statement and then remove useless return keyword}} | ||
| .forEach(x -> { // Compliant - lambda body spans multiple lines, block form is kept for readability | ||
| System.out.println(x + 11); | ||
| }); | ||
| IntStream.range(1, 5).map(x -> { // Compliant - non-expression statement | ||
| if (x % 2 == 0) return 0; | ||
| else return 1; | ||
| }); | ||
| IntStream.range(1, 5).forEach(x -> { | ||
| try { | ||
| x = x/0; | ||
| } catch (Exception e) { | ||
| System.out.println(x); | ||
| } | ||
| }); | ||
| IntStream.range(1, 5).forEach(x -> { | ||
| while(true) { | ||
| } | ||
| }); | ||
| // Nested block | ||
| IntStream.range(1, 5).map(x -> { { { return x + 1; } } }); // Noncompliant {{Remove useless curly braces around statement}} | ||
| } | ||
|
|
||
| // Block lambda binds to RowCallbackHandler (void); simplifying to expression lambda | ||
| // would be ambiguous with ResultSetExtractor since merge() returns a value | ||
| void springJdbcQuery(JdbcTemplate jdbc, NamedParameterJdbcTemplate namedJdbc) { | ||
| Map<Long, Long> countByRecipient = new HashMap<>(); | ||
| jdbc.query("SELECT recipient_id, cnt FROM t", rs -> { countByRecipient.merge(rs.getLong("recipient_id"), rs.getLong("cnt"), Long::sum); }); // Compliant | ||
| namedJdbc.query("SELECT recipient_id, cnt FROM t", Collections.emptyMap(), | ||
| rs -> { countByRecipient.merge(rs.getLong("recipient_id"), rs.getLong("cnt"), Long::sum); }); // Compliant | ||
| jdbc.query("SELECT name FROM t", | ||
| (rs, rowNum) -> { return rs.getString("name"); }); // Noncompliant {{Remove useless curly braces around statement and then remove useless return keyword}} | ||
| } | ||
|
|
||
| @FunctionalInterface | ||
| interface ThrowingRunnable { | ||
| void run() throws Throwable; | ||
| } | ||
|
|
||
| void process(ThrowingRunnable r) throws Throwable { | ||
| r.run(); | ||
| } | ||
|
|
||
| // MethodHandle.invokeExact() and MethodHandle.invoke() are signature-polymorphic | ||
| void methodHandleInvocations(MethodHandle handle) throws Throwable { | ||
| process(() -> { handle.invokeExact(); }); | ||
| process(() -> { handle.invoke(); }); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package checks; | ||
|
|
||
| import java.lang.invoke.MethodHandle; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import org.springframework.jdbc.core.JdbcTemplate; | ||
| import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; | ||
|
|
||
| public class LambdaSingleExpressionCheckSampleWithoutSemantic { | ||
|
|
||
| @FunctionalInterface | ||
| interface ThrowingRunnable { | ||
| void run() throws Throwable; | ||
| } | ||
|
|
||
| void process(ThrowingRunnable r) throws Throwable { | ||
| r.run(); | ||
| } | ||
|
|
||
| void springJdbcQuery(JdbcTemplate jdbc, NamedParameterJdbcTemplate namedJdbc) { | ||
| Map<Long, Long> countByRecipient = new HashMap<>(); | ||
| jdbc.query("SELECT recipient_id, cnt FROM t", rs -> { countByRecipient.merge(rs.getLong("recipient_id"), rs.getLong("cnt"), Long::sum); }); // Noncompliant | ||
| namedJdbc.query("SELECT recipient_id, cnt FROM t", Collections.emptyMap(), | ||
| rs -> { countByRecipient.merge(rs.getLong("recipient_id"), rs.getLong("cnt"), Long::sum); }); // Noncompliant | ||
| } | ||
|
|
||
| void methodHandleInvocations(MethodHandle handle) throws Throwable { | ||
| process(() -> { handle.invokeExact(); }); | ||
| process(() -> { handle.invoke(); }); | ||
| } | ||
|
gitar-bot[bot] marked this conversation as resolved.
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,11 +17,17 @@ | |||||||||||||||||||||||||||||||||||||||||||
| package org.sonar.java.checks; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.check.Rule; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.java.model.LineUtils; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.JavaVersionAwareVisitor; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.JavaVersion; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.semantic.MethodMatchers; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.BlockTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.ExpressionStatementTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.ExpressionTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.LambdaExpressionTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.MethodInvocationTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.ReturnStatementTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.StatementTree; | ||||||||||||||||||||||||||||||||||||||||||||
| import org.sonar.plugins.java.api.tree.Tree; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -31,6 +37,20 @@ | |||||||||||||||||||||||||||||||||||||||||||
| @Rule(key = "S1602") | ||||||||||||||||||||||||||||||||||||||||||||
| public class LambdaSingleExpressionCheck extends IssuableSubscriptionVisitor implements JavaVersionAwareVisitor { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private static final MethodMatchers SPRING_JDBC_QUERY_MATCHER = MethodMatchers.create() | ||||||||||||||||||||||||||||||||||||||||||||
| .ofSubTypes( | ||||||||||||||||||||||||||||||||||||||||||||
| "org.springframework.jdbc.core.JdbcOperations", | ||||||||||||||||||||||||||||||||||||||||||||
| "org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations") | ||||||||||||||||||||||||||||||||||||||||||||
| .names("query") | ||||||||||||||||||||||||||||||||||||||||||||
| .withAnyParameters() | ||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private static final MethodMatchers METHOD_HANDLE_INVOKE_MATCHER = MethodMatchers.create() | ||||||||||||||||||||||||||||||||||||||||||||
| .ofTypes("java.lang.invoke.MethodHandle") | ||||||||||||||||||||||||||||||||||||||||||||
| .names("invoke", "invokeExact") | ||||||||||||||||||||||||||||||||||||||||||||
| .withAnyParameters() | ||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||
| public boolean isCompatibleWithJavaVersion(JavaVersion version) { | ||||||||||||||||||||||||||||||||||||||||||||
| return version.isJava8Compatible(); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,7 +65,10 @@ public List<Tree.Kind> nodesToVisit() { | |||||||||||||||||||||||||||||||||||||||||||
| public void visitNode(Tree tree) { | ||||||||||||||||||||||||||||||||||||||||||||
| LambdaExpressionTree lambdaExpressionTree = (LambdaExpressionTree) tree; | ||||||||||||||||||||||||||||||||||||||||||||
| Tree lambdaBody = lambdaExpressionTree.body(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (isBlockWithOneStatement(lambdaBody)) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (isBlockWithOneStatement(lambdaBody) | ||||||||||||||||||||||||||||||||||||||||||||
| && !hasMultilineBody(lambdaExpressionTree) | ||||||||||||||||||||||||||||||||||||||||||||
| && !isInsideSpringJdbcQuery(lambdaExpressionTree) | ||||||||||||||||||||||||||||||||||||||||||||
| && !isSingleMethodHandleInvocation(lambdaExpressionTree)) { | ||||||||||||||||||||||||||||||||||||||||||||
| String message = "Remove useless curly braces around statement"; | ||||||||||||||||||||||||||||||||||||||||||||
| if (singleStatementIsReturn(lambdaExpressionTree)) { | ||||||||||||||||||||||||||||||||||||||||||||
| message += " and then remove useless return keyword"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -74,4 +97,33 @@ private static boolean singleStatementIsReturn(LambdaExpressionTree lambdaExpres | |||||||||||||||||||||||||||||||||||||||||||
| private static boolean isReturnStatement(Tree tree) { | ||||||||||||||||||||||||||||||||||||||||||||
| return tree.is(Tree.Kind.RETURN_STATEMENT); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private static boolean hasMultilineBody(LambdaExpressionTree lambda) { | ||||||||||||||||||||||||||||||||||||||||||||
| BlockTree block = (BlockTree) lambda.body(); | ||||||||||||||||||||||||||||||||||||||||||||
| return LineUtils.startLine(block.openBraceToken()) != LineUtils.startLine(block.closeBraceToken()); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
gitar-bot[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private static boolean isInsideSpringJdbcQuery(LambdaExpressionTree lambda) { | ||||||||||||||||||||||||||||||||||||||||||||
| if (lambda.parameters().size() != 1) { | ||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Tree parent = lambda.parent(); | ||||||||||||||||||||||||||||||||||||||||||||
| if (parent != null && parent.is(Tree.Kind.ARGUMENTS)) { | ||||||||||||||||||||||||||||||||||||||||||||
| parent = parent.parent(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return parent != null && parent.is(Tree.Kind.METHOD_INVOCATION) | ||||||||||||||||||||||||||||||||||||||||||||
| && SPRING_JDBC_QUERY_MATCHER.matches((MethodInvocationTree) parent); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+114
to
+115
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
gitar-bot[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private static boolean isSingleMethodHandleInvocation(LambdaExpressionTree lambda) { | ||||||||||||||||||||||||||||||||||||||||||||
| StatementTree statement = ((BlockTree) lambda.body()).body().get(0); | ||||||||||||||||||||||||||||||||||||||||||||
| ExpressionTree expression = null; | ||||||||||||||||||||||||||||||||||||||||||||
| if (statement.is(Tree.Kind.EXPRESSION_STATEMENT)) { | ||||||||||||||||||||||||||||||||||||||||||||
| expression = ((ExpressionStatementTree) statement).expression(); | ||||||||||||||||||||||||||||||||||||||||||||
| } else if (statement.is(Tree.Kind.RETURN_STATEMENT)) { | ||||||||||||||||||||||||||||||||||||||||||||
| expression = ((ReturnStatementTree) statement).expression(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return expression != null && expression.is(Tree.Kind.METHOD_INVOCATION) | ||||||||||||||||||||||||||||||||||||||||||||
| && METHOD_HANDLE_INVOKE_MATCHER.matches((MethodInvocationTree) expression); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+119
to
+127
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.