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:

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