Skip to content

SONARJAVA-6827: Implement S9357 Anonymous classes on functional interfaces should be lambdas - #5995

Merged
romainbrenguier merged 8 commits into
masterfrom
romain/new-rule-s9357-sonarjava-6827
Aug 25, 2026
Merged

SONARJAVA-6827: Implement S9357 Anonymous classes on functional interfaces should be lambdas#5995
romainbrenguier merged 8 commits into
masterfrom
romain/new-rule-s9357-sonarjava-6827

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Implement S9357: Anonymous classes on functional interfaces should be lambdas.

  • S9357 scope is set to Tests to complement S1604 (scope: Main) without double-reporting on main code
  • Shared detection logic extracted into AbstractAnonymousClassToLambdaCheck base class, used by both S9357 and S1604, eliminating code duplication
  • Test samples moved to test source path to match the Tests scope

Test plan

  • S9357 unit tests pass (with and without semantic)
  • S1604 unit tests pass (pre-existing failure on Function<Object, Date> test case unrelated to this change)
  • CheckList generation correctly places S9357 in JAVA_TEST_CHECKS and S1604 in JAVA_MAIN_CHECKS
  • CI ruling tests (will need a new ruling fix PR after scope change)

…faces should be lambdas

Detects anonymous inner classes implementing functional interfaces (single
abstract method) that can be replaced with lambda expressions. Mirrors the
detection logic of S1604 but applies to all code (main and test scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

SONARJAVA-6827

Comment thread sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.json Outdated
@datadog-sonarsource

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5996

Please review and merge it into your branch.

Extract shared detection logic into AbstractAnonymousClassToLambdaCheck
base class used by both AnonymousClassOnFunctionalInterfaceCheck (S9357)
and AnonymousClassShouldBeLambdaCheck (S1604).

Change S9357 scope from "All" to "Tests" to avoid double-reporting
on main code where S1604 already applies. Move S9357 test samples
to test source path accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Ruling needs updating. A fix PR has been created: #5996

Please review and merge it into your branch.

github-actions Bot and others added 2 commits August 24, 2026 09:24
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Rules with scope "Tests" must include the "tests" tag to pass
GeneratedCheckListTest validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 3 rule files: 0 issues removed, 71 issues added.

S9357 (java) on commons-beanutils - 0 issues removed, 1 issues added - new ruling file

Added src/test/java/org/apache/commons/beanutils2/bugs/Jira509TestCase.java (line 60)

        55 |     public void test_concurrent() throws InterruptedException {
        56 |         final List<Class<?>> classList = Arrays.asList(Map.class, HashMap.class, Collections.class, Arrays.class,
        57 |                 Collection.class, Set.class, ArrayList.class, List.class, HashSet.class);
        58 | 
        59 |         // All daemon threads.
>>>     60 |         final ExecutorService executor = Executors.newFixedThreadPool(100, new ThreadFactory() {
        61 | 
        62 |             @Override
        63 |             public Thread newThread(final Runnable r) {
        64 |                 final Thread thread = new Thread(r);
        65 |                 thread.setDaemon(true);
S9357 (java) on eclipse-jetty - 0 issues removed, 31 issues added - new ruling file

Added jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java (line 150)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java (line 203)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java (line 238)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java)

Added jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java (line 285)

(source file not found at this revision: jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java (line 219)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java (line 293)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java (line 301)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java (line 77)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java (line 139)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java (line 177)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java (line 85)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java (line 107)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java (line 123)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/thread/SchedulerTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java (line 77)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java)

Added jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java (line 113)

(source file not found at this revision: jetty-util/src/test/java/org/eclipse/jetty/util/thread/SweeperTest.java)
S9357 (java) on sonar-server - 0 issues removed, 39 issues added - new ruling file

Added src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java (line 64)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/container/TaskContainerImplTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/component/CrawlerDepthLimitTest.java (line 46)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/component/CrawlerDepthLimitTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/component/CrawlerDepthLimitTest.java (line 52)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/component/CrawlerDepthLimitTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java (line 184)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulatorTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java (line 86)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java (line 210)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImplTest.java (line 267)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImplTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryRule.java (line 65)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryRule.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureTest.java (line 98)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/source/SourceHashRepositoryImplTest.java (line 83)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/source/SourceHashRepositoryImplTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/step/PurgeDatastoresStepTest.java (line 93)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/step/PurgeDatastoresStepTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/step/PurgeDatastoresStepTest.java (line 111)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/step/PurgeDatastoresStepTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationStepsTest.java (line 42)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationStepsTest.java)

Added src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationStepsTest.java (line 63)

(source file not found at this revision: src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationStepsTest.java)

Added src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java (line 235)

(source file not found at this revision: src/test/java/org/sonar/server/duplication/ws/DuplicationsParserTest.java)

The abstract base class extracted in the refactoring commit is counted
by the file-scanning logic but excluded from the generated check list
(no @rule annotation), causing a count mismatch in
GeneratedCheckListTest.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeTree;

public abstract class AbstractAnonymousClassToLambdaCheck extends BaseTreeVisitor implements JavaFileScanner, JavaVersionAwareVisitor {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't declare any abstract class. This introduce coupling between rules. To avoid duplicating code, you may use utility classes.

romainbrenguier and others added 2 commits August 24, 2026 13:03
…upling

Replace AbstractAnonymousClassToLambdaCheck with AnonymousClassToLambdaUtils
utility class. Both AnonymousClassShouldBeLambdaCheck (S1604) and
AnonymousClassOnFunctionalInterfaceCheck (S9357) are now standalone checks
that delegate shared logic to the utility class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…upling

Move the shared visitor logic from AnonymousClassShouldBeLambdaCheck and
AnonymousClassOnFunctionalInterfaceCheck into a static method in
AnonymousClassToLambdaUtils. Both checks are now thin wrappers that
delegate to the utility, eliminating code duplication without introducing
an abstract class coupling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romainbrenguier
romainbrenguier marked this pull request as ready for review August 24, 2026 12:03

@Override
public boolean isCompatibleWithJavaVersion(JavaVersion version) {
return version.isJava8Compatible();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Exclude S9357 from AutoScan\n\nAutoScan generally lacks sonar.java.source, so an unspecified version is treated as Java 8-compatible. S9357 may therefore recommend lambdas in Java 7 test code, where lambdas are unsupported. S1604 is already excluded from AutoScan for this exact reason. Add AnonymousClassOnFunctionalInterfaceCheck.class alongside it in JAVA_CHECKS_NOT_WORKING_FOR_AUTOSCAN.

AutoScan lacks sonar.java.source, so unspecified version is treated as
Java 8-compatible. S9357 may recommend lambdas in Java 7 test code where
lambdas are unsupported. This mirrors the existing S1604 exclusion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Implements rule S9357 to flag anonymous classes on functional interfaces in test code, addressing the check duplication and double-reporting findings by extracting shared base logic with S1604. No issues found.

✅ 3 resolved
Quality: New check duplicates S1604 logic verbatim

📄 java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java:43-57
AnonymousClassOnFunctionalInterfaceCheck is a byte-for-byte copy of AnonymousClassShouldBeLambdaCheck (S1604), differing only in the @rule key and class name. This duplicates ~150 lines including the UsesThisInstanceVisitor, isSAM/hasSingleAbstractMethodInHierarchy logic, so any future bug fix or improvement must be applied twice and will drift. Extract the shared detection logic into a common base/utility class that both S1604 and S9357 delegate to, so the two rules only differ in their @rule key and scope metadata.

Bug: S9357 double-reports with S1604 on main code

📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9357.json:17 📄 java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java:74-76
S9357 has scope "All" (main + test) while S1604 has scope "Main", and both implement identical detection with an identical message. When both rules are active in a quality profile, every anonymous-class-to-lambda opportunity in main code will be flagged twice (once as S1604, once as S9357), producing duplicate issues for users. Confirm this is intended; if S9357 is only meant to extend S1604's coverage to test code, its scope/activation should avoid overlapping with S1604 on main code (or S1604 should be deprecated in favor of S9357).

Bug: S9357 check class may not be registered in the check list

📄 java-checks/src/main/java/org/sonar/java/checks/AnonymousClassOnFunctionalInterfaceCheck.java:21-22
AnonymousClassOnFunctionalInterfaceCheck (S9357) does not appear in any check-registration list — a codebase-wide search finds it only in its own source, test, and sample files, whereas its sibling AnonymousClassShouldBeLambdaCheck (S1604) is explicitly listed in CheckListGenerator. If the new rule class is not added to the plugin's registered checks, S9357 will never execute at runtime even though its RSPEC metadata and tests exist. Verify the check is registered (e.g. added to the generated/explicit check list) so the new rule actually runs.

Implementation Status ✅ 1 of 1 objectives covered
SONARJAVA-6827 - 1 of 1 objectives covered

This PR implements rule S9357 to detect anonymous classes on functional interfaces that should be lambdas.

✅ 1 covered here
  • ✅ Implement rule S9357 to detect anonymous classes on functional interfaces that should be lambdas
Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier enabled auto-merge (squash) August 25, 2026 09:33
@romainbrenguier
romainbrenguier merged commit 2671b1d into master Aug 25, 2026
17 checks passed
@romainbrenguier
romainbrenguier deleted the romain/new-rule-s9357-sonarjava-6827 branch August 25, 2026 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants