From 0ec16ca9a2e24948f043ad5cc7e3ac4309b360e0 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 26 Oct 2026 12:20:35 +0100 Subject: [PATCH] SONARJAVA-7239 Improve S9357: do not raise issues on projects using GWT older than 2.8 GWT supports lambda expressions only since version 2.8, so anonymous classes cannot be replaced by lambdas in projects depending on an older GWT version. Also add the missing variable type in the rule description example. Co-Authored-By: Claude Opus 5.5 (1M context) --- ...ssOnFunctionalInterfaceCheckGwtSample.java | 19 +++++++++++ ...nymousClassOnFunctionalInterfaceCheck.java | 18 +++++++++- ...usClassOnFunctionalInterfaceCheckTest.java | 33 +++++++++++++++++++ .../org/sonar/l10n/java/rules/java/S9357.html | 5 +-- 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 java-checks-test-sources/default/src/main/java/checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java diff --git a/java-checks-test-sources/default/src/main/java/checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java b/java-checks-test-sources/default/src/main/java/checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java new file mode 100644 index 00000000000..8bedd6ef483 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java @@ -0,0 +1,19 @@ +package checks; + +class AnonymousClassOnFunctionalInterfaceCheckGwtSample { + + interface ClickHandler { + void onClick(String event); + } + + void addClickHandler(ClickHandler handler) { + } + + void register() { + addClickHandler(new ClickHandler() { // Noncompliant {{Make this anonymous inner class a lambda}} + @Override + public void onClick(String event) { + } + }); + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java b/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java index bbb6c9222f8..50883e6794b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java @@ -16,21 +16,37 @@ */ package org.sonar.java.checks; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.AnonymousClassToLambdaUtils; +import org.sonar.plugins.java.api.DependencyVersionAware; import org.sonar.plugins.java.api.JavaFileScanner; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.JavaVersionAwareVisitor; +import org.sonar.plugins.java.api.Version; @Rule(key = "S9357") -public class AnonymousClassOnFunctionalInterfaceCheck implements JavaFileScanner, JavaVersionAwareVisitor { +public class AnonymousClassOnFunctionalInterfaceCheck implements JavaFileScanner, JavaVersionAwareVisitor, DependencyVersionAware { @Override public boolean isCompatibleWithJavaVersion(JavaVersion version) { return version.isJava8Compatible(); } + /** + * GWT supports lambda expressions only since version 2.8: the rule is disabled for projects depending on an older GWT version. + */ + @Override + public boolean isCompatibleWithDependencies(Function> dependencyFinder) { + return Stream.of("gwt-user", "gwt-dev", "gwt-servlet") + .map(dependencyFinder) + .flatMap(Optional::stream) + .allMatch(version -> version.isGreaterThanOrEqualTo("2.8")); + } + @Override public void scanFile(JavaFileScannerContext context) { AnonymousClassToLambdaUtils.scanForAnonymousClassConvertibleToLambda(this, context); diff --git a/java-checks/src/test/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheckTest.java index f575c3a9bd1..70acc52e734 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheckTest.java @@ -16,9 +16,12 @@ */ package org.sonar.java.checks; +import java.io.File; +import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; import static org.sonar.java.checks.verifier.TestUtils.testCodeSourcesPath; class AnonymousClassOnFunctionalInterfaceCheckTest { @@ -41,4 +44,34 @@ void test_without_semantic() { .withoutSemantic() .verifyIssues(); } + + @Test + void test_gwt_supporting_lambdas() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java")) + .withCheck(new AnonymousClassOnFunctionalInterfaceCheck()) + .withJavaVersion(8) + .withClassPath(List.of(new File("gwt-user-2.8.0.jar"))) + .verifyIssues(); + } + + @Test + void test_gwt_not_supporting_lambdas() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java")) + .withCheck(new AnonymousClassOnFunctionalInterfaceCheck()) + .withJavaVersion(8) + .withClassPath(List.of(new File("gwt-user-2.7.0.jar"))) + .verifyNoIssues(); + } + + @Test + void test_gwt_dev_not_supporting_lambdas() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/AnonymousClassOnFunctionalInterfaceCheckGwtSample.java")) + .withCheck(new AnonymousClassOnFunctionalInterfaceCheck()) + .withJavaVersion(8) + .withClassPath(List.of(new File("gwt-user-2.8.0.jar"), new File("gwt-dev-2.6.1.jar"))) + .verifyNoIssues(); + } } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.html index 5cf55cfe706..de8a17cf54c 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.html @@ -8,7 +8,7 @@

Why is this an issue?

Before Java 8 introduced lambda expressions, the only way to implement a functional interface inline was through verbose anonymous inner classes. This approach requires significant boilerplate code that obscures the actual logic:

-taskVariable = new InterfaceImplementation() {
+InterfaceImplementation taskVariable = new InterfaceImplementation() {
     @Override
     public void methodName() {
         processData();
@@ -18,7 +18,7 @@ 

Why is this an issue?

Java 8 introduced lambda expressions specifically to simplify this pattern. A lambda expression is a concise way to represent a functional interface using an expression. The same functionality can be written as:

-taskVariable = () -> processData();
+InterfaceImplementation taskVariable = () -> processData();
 

Using lambda expressions instead of anonymous inner classes offers several benefits:

    @@ -45,6 +45,7 @@

    Exceptions

    carry extra members.
  • The anonymous class has serialization requirements (e.g., implements Serializable explicitly), because lambda serialization has different semantics and caveats.
  • +
  • The project depends on a GWT version older than 2.8, because the GWT compiler does not support lambda expressions before that version.

In Java, these single-method interfaces are called functional interfaces. Common examples include Runnable, Callable, Comparator, and the many interfaces in the java.util.function package. Lambda expressions were