Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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) {
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Optional<Version>> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h2>Why is this an issue?</h2>
<p>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:</p>
<pre>
taskVariable = new InterfaceImplementation() {
InterfaceImplementation taskVariable = new InterfaceImplementation() {
@Override
public void methodName() {
processData();
Expand All @@ -18,7 +18,7 @@ <h2>Why is this an issue?</h2>
<p>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:</p>
<pre>
taskVariable = () -&gt; processData();
InterfaceImplementation taskVariable = () -&gt; processData();
</pre>
<p>Using lambda expressions instead of anonymous inner classes offers several benefits:</p>
<ul>
Expand All @@ -45,6 +45,7 @@ <h3>Exceptions</h3>
carry extra members.</li>
<li>The anonymous class has serialization requirements (e.g., implements <code>Serializable</code> explicitly), because lambda serialization has
different semantics and caveats.</li>
<li>The project depends on a GWT version older than 2.8, because the GWT compiler does not support lambda expressions before that version.</li>
</ul>
<p>In Java, these single-method interfaces are called <strong>functional interfaces</strong>. Common examples include <code>Runnable</code>,
<code>Callable</code>, <code>Comparator</code>, and the many interfaces in the <code>java.util.function</code> package. Lambda expressions were
Expand Down
Loading