From 659b8c0589b9d4ea089421f894af5480cdcab623 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 10 Sep 2026 22:28:36 +0200 Subject: [PATCH 1/3] SONARJAVA-6673 S1602: Fix false positives for Spring JDBC query overloads, MethodHandle signature-polymorphic invocations, and multiline lambda bodies Co-Authored-By: Claude Sonnet 4.6 --- .../java-S1602.json | 5 -- .../java-S2139.json | 1 - .../resources/eclipse-jetty/java-S1602.json | 5 -- .../resources/eclipse-jetty/java-S9142.json | 1 - .../resources/eclipse-jetty/java-S9346.json | 1 - .../resources/sonar-server/java-S9142.json | 1 - ...aSingleExpressionCheckNoVersionSample.java | 20 +++---- .../LambdaSingleExpressionCheckSample.java | 58 +++++++++++++++++++ ...eExpressionCheckSampleWithoutSemantic.java | 33 +++++++++++ .../checks/LambdaSingleExpressionCheck.java | 51 +++++++++++++++- .../checks/LambdaSingleExpressionCheck.java | 35 ----------- .../LambdaSingleExpressionCheckTest.java | 8 ++- 12 files changed, 153 insertions(+), 66 deletions(-) delete mode 100644 its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S1602.json delete mode 100644 its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2139.json delete mode 100644 its/ruling/src/test/resources/eclipse-jetty/java-S1602.json delete mode 100644 its/ruling/src/test/resources/eclipse-jetty/java-S9142.json delete mode 100644 its/ruling/src/test/resources/eclipse-jetty/java-S9346.json delete mode 100644 its/ruling/src/test/resources/sonar-server/java-S9142.json rename java-checks/src/test/files/checks/LambdaSingleExpressionCheck_no_version.java => java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckNoVersionSample.java (60%) create mode 100644 java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java create mode 100644 java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSampleWithoutSemantic.java delete mode 100644 java-checks/src/test/files/checks/LambdaSingleExpressionCheck.java diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S1602.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S1602.json deleted file mode 100644 index a025176e325..00000000000 --- a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S1602.json +++ /dev/null @@ -1,5 +0,0 @@ -{ -"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java": [ -164 -] -} diff --git a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2139.json b/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2139.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/its/ruling/src/test/resources/eclipse-jetty-similar-to-main/java-S2139.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S1602.json b/its/ruling/src/test/resources/eclipse-jetty/java-S1602.json deleted file mode 100644 index a025176e325..00000000000 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S1602.json +++ /dev/null @@ -1,5 +0,0 @@ -{ -"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java": [ -164 -] -} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9142.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9142.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S9142.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9346.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9346.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/its/ruling/src/test/resources/eclipse-jetty/java-S9346.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/its/ruling/src/test/resources/sonar-server/java-S9142.json b/its/ruling/src/test/resources/sonar-server/java-S9142.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/its/ruling/src/test/resources/sonar-server/java-S9142.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/java-checks/src/test/files/checks/LambdaSingleExpressionCheck_no_version.java b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckNoVersionSample.java similarity index 60% rename from java-checks/src/test/files/checks/LambdaSingleExpressionCheck_no_version.java rename to java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckNoVersionSample.java index fee4fe24aa4..37b725383ed 100644 --- a/java-checks/src/test/files/checks/LambdaSingleExpressionCheck_no_version.java +++ b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckNoVersionSample.java @@ -1,13 +1,16 @@ -class A { +package checks; + +import java.util.stream.IntStream; + +public class LambdaSingleExpressionCheckNoVersionSample { 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 (sonar.java.source not set. Assuming 8 or greater.)}} // ^ - .forEach(x -> { // Noncompliant {{Remove useless curly braces around statement (sonar.java.source not set. Assuming 8 or greater.)}} + .forEach(x -> { // Compliant - lambda body spans multiple lines, block form is kept for readability System.out.println(x + 11); }); - //Non-Expression statement : - IntStream.range(1, 5).map(x -> { + IntStream.range(1, 5).map(x -> { // Compliant - non-expression statement if (x % 2 == 0) return 0; else return 1; }); @@ -22,15 +25,6 @@ public void method() { while(true) { } }); - - //Nested blocks - IntStream.range(1, 5).map(x -> { // Noncompliant {{Remove useless curly braces around statement (sonar.java.source not set. Assuming 8 or greater.)}} - { - { - return x + 1; - } - } - }); } } diff --git a/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java new file mode 100644 index 00000000000..c43721009bf --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java @@ -0,0 +1,58 @@ +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) { + } + }); + } + + // 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 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 + } + + @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(); }); + } + +} diff --git a/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSampleWithoutSemantic.java b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSampleWithoutSemantic.java new file mode 100644 index 00000000000..f753a1e7bda --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSampleWithoutSemantic.java @@ -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 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(); }); + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java index 7bea2013ad2..54ba073ee50 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java @@ -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 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,30 @@ 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()); + } + + private static boolean isInsideSpringJdbcQuery(LambdaExpressionTree lambda) { + 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); + } + + 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); + } } diff --git a/java-checks/src/test/files/checks/LambdaSingleExpressionCheck.java b/java-checks/src/test/files/checks/LambdaSingleExpressionCheck.java deleted file mode 100644 index be9d920a23f..00000000000 --- a/java-checks/src/test/files/checks/LambdaSingleExpressionCheck.java +++ /dev/null @@ -1,35 +0,0 @@ -class A { - 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 -> { // Noncompliant {{Remove useless curly braces around statement}} - System.out.println(x + 11); - }); - //Non-Expression statement : - IntStream.range(1, 5).map(x -> { - 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 blocks - IntStream.range(1, 5).map(x -> { // Noncompliant {{Remove useless curly braces around statement}} - { - { - return x + 1; - } - } - }); - } - -} diff --git a/java-checks/src/test/java/org/sonar/java/checks/LambdaSingleExpressionCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/LambdaSingleExpressionCheckTest.java index bbe05d83d96..4c6a3925bfb 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/LambdaSingleExpressionCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/LambdaSingleExpressionCheckTest.java @@ -19,12 +19,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + class LambdaSingleExpressionCheckTest { @Test void no_version() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/LambdaSingleExpressionCheck_no_version.java") + .onFile(mainCodeSourcesPath("checks/LambdaSingleExpressionCheckNoVersionSample.java")) .withCheck(new LambdaSingleExpressionCheck()) .verifyIssues(); } @@ -32,7 +34,7 @@ void no_version() { @Test void java_8() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/LambdaSingleExpressionCheck.java") + .onFile(mainCodeSourcesPath("checks/LambdaSingleExpressionCheckSample.java")) .withCheck(new LambdaSingleExpressionCheck()) .withJavaVersion(8) .verifyIssues(); @@ -41,7 +43,7 @@ void java_8() { @Test void test_without_semantic() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/LambdaSingleExpressionCheck_no_version.java") + .onFile(mainCodeSourcesPath("checks/LambdaSingleExpressionCheckSampleWithoutSemantic.java")) .withCheck(new LambdaSingleExpressionCheck()) .withoutSemantic() .verifyIssues(); From 37b8e81809107cd1f82e6432bd80210ac8a68611 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Thu, 10 Sep 2026 23:24:35 +0200 Subject: [PATCH 2/3] SONARJAVA-6673 S1602: Tighten Spring JDBC exemption to single-parameter lambdas and update rule description Co-Authored-By: Claude Sonnet 4.6 --- .../LambdaSingleExpressionCheckSample.java | 7 +++++- .../checks/LambdaSingleExpressionCheck.java | 3 +++ .../org/sonar/l10n/java/rules/java/S1602.html | 23 ++++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java index c43721009bf..620af87f1ce 100644 --- a/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/LambdaSingleExpressionCheckSample.java @@ -30,6 +30,8 @@ public void method() { 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 @@ -37,7 +39,10 @@ public void method() { void springJdbcQuery(JdbcTemplate jdbc, NamedParameterJdbcTemplate namedJdbc) { Map 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 + 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 diff --git a/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java index 54ba073ee50..e5112c2da32 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/LambdaSingleExpressionCheck.java @@ -104,6 +104,9 @@ private static boolean hasMultilineBody(LambdaExpressionTree lambda) { } 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(); diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html index 90af7dad958..d0658fed821 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html @@ -16,6 +16,17 @@

How to fix it

return statement.
  • If the code block consists of a single statement that is not a return statement, replace the code block with that statement.
  • +

    Exceptions

    +

    This rule does not flag block-form lambdas in the following cases:

    +
      +
    • Multiline body — if the opening { and closing } are on different lines, a block form is kept for + readability.
    • +
    • Spring JDBC query() with a single-parameter lambda — converting such a lambda to an expression form may silently + change overload resolution.
    • +
    • MethodHandle.invoke() / MethodHandle.invokeExact() — these are signature-polymorphic methods. + Converting such lambdas to an expression form changes the inferred return type, which can cause a WrongMethodTypeException at + runtime.
    • +

    Code examples

    Noncompliant code example

    @@ -27,10 +38,20 @@ 

    Compliant solution

    Noncompliant code example

    -x -> {System.out.println(x+1);} // Noncompliant, replace code block with statement
    +x -> { System.out.println(x+1); } // Noncompliant, replace code block with statement
     

    Compliant solution

     x -> System.out.println(x+1)    // Compliant
     
    +

    Compliant — multiline body and special cases

    +
    +entries.forEach(e -> {
    +  System.out.println(e.getKey() + ": " + e.getValue());
    +});
    +
    +jdbcTemplate.query("SELECT id, cnt FROM t", rs -> { map.merge(rs.getLong("id"), rs.getLong("cnt"), Long::sum); });
    +
    +process(() -> { handle.invokeExact(); });
    +
    From 5fea1f58ad02463c8fe2834b7ea2aaeee75e39f9 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Fri, 11 Sep 2026 10:01:30 +0200 Subject: [PATCH 3/3] Updated rspec --- .../org/sonar/l10n/java/rules/java/S1602.html | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html index d0658fed821..7efea65fa38 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1602.html @@ -10,12 +10,6 @@

    Why is this an issue?

    than one statement. However, when the code block consists of only one statement (which may or may not be a return statement), it can be rewritten using expression notation.

    This convention exists because expression notation has a cleaner, more concise, functional programming style and is regarded as more readable.

    -

    How to fix it

    -
      -
    • If the code block consists only of a return statement, replace the code block with the argument expression from the - return statement.
    • -
    • If the code block consists of a single statement that is not a return statement, replace the code block with that statement.
    • -

    Exceptions

    This rule does not flag block-form lambdas in the following cases:

      @@ -27,6 +21,22 @@

      Exceptions

      Converting such lambdas to an expression form changes the inferred return type, which can cause a WrongMethodTypeException at runtime.
    +
    +entries.forEach(e -> { // Compliant, multiline body
    +  System.out.println(e.getKey() + ": " + e.getValue());
    +});
    +
    +// Compliant, expression form would resolve to ResultSetExtractor instead of RowCallbackHandler
    +jdbcTemplate.query("SELECT id, cnt FROM t", rs -> { map.merge(rs.getLong("id"), rs.getLong("cnt"), Long::sum); });
    +
    +process(() -> { handle.invokeExact(); }); // Compliant, signature-polymorphic invocation stays void
    +
    +

    How to fix it

    +
      +
    • If the code block consists only of a return statement, replace the code block with the argument expression from the + return statement.
    • +
    • If the code block consists of a single statement that is not a return statement, replace the code block with that statement.
    • +

    Code examples

    Noncompliant code example

    @@ -44,14 +54,4 @@ 

    Compliant solution

     x -> System.out.println(x+1)    // Compliant
     
    -

    Compliant — multiline body and special cases

    -
    -entries.forEach(e -> {
    -  System.out.println(e.getKey() + ": " + e.getValue());
    -});
    -
    -jdbcTemplate.query("SELECT id, cnt FROM t", rs -> { map.merge(rs.getLong("id"), rs.getLong("cnt"), Long::sum); });
    -
    -process(() -> { handle.invokeExact(); });
    -