From 9a3ad61df22c363361549662786b51fe11ad61de Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 23 Jun 2026 10:53:19 +0200 Subject: [PATCH 01/22] WIP: S8909 implementation (incomplete after 5 attempts) This commit contains partial work that failed to complete. Continuing with SA-CI and PR creation. --- java-checks-test-sources/default/pom.xml | 6 + ...heKeyGeneratorInstantiableCheckSample.java | 158 ++++++++++++++++++ .../CacheKeyGeneratorInstantiableCheck.java | 99 +++++++++++ ...acheKeyGeneratorInstantiableCheckTest.java | 33 ++++ 4 files changed, 296 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index 0513451a9c3..b14d9092072 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -186,6 +186,12 @@ jar provided + + io.quarkus + quarkus-cache + 3.2.0.Final + provided + io.reactivex rxjava diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java new file mode 100644 index 00000000000..8eeedecb5f8 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -0,0 +1,158 @@ +package checks.quarkus; + +import io.quarkus.cache.CacheKeyGenerator; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import java.lang.reflect.Method; + +class NoncompliantBasic implements CacheKeyGenerator { // Noncompliant {{Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor.}} +// ^^^^^^^^^^^^^^^^^ + private final ConfigService configService; + + public NoncompliantBasic(ConfigService configService) { + this.configService = configService; + } + + @Override + public Object generate(Method method, Object... methodParams) { + return configService.getPrefix() + methodParams[0]; + } +} + +class NoncompliantMultipleDependencies implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private final DatabaseService dbService; + private final CacheConfig config; + + public NoncompliantMultipleDependencies(DatabaseService dbService, CacheConfig config) { + this.dbService = dbService; + this.config = config; + } + + @Override + public Object generate(Method method, Object... methodParams) { + return dbService.format(methodParams[0]); + } +} + +class NoncompliantMultipleConstructors implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private final String prefix; + + public NoncompliantMultipleConstructors(String prefix) { + this.prefix = prefix; + } + + public NoncompliantMultipleConstructors(String prefix, int timeout) { + this.prefix = prefix; + } + + @Override + public Object generate(Method method, Object... methodParams) { + return prefix + methodParams[0]; + } +} + +class NoncompliantPrivateConstructor implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + private NoncompliantPrivateConstructor() {} + + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + +@ApplicationScoped +class CompliantApplicationScoped implements CacheKeyGenerator { + @Inject + ConfigService configService; + + @Override + public Object generate(Method method, Object... methodParams) { + String prefix = configService.getPrefix(); + return prefix + methodParams[0]; + } +} + +@Dependent +class CompliantDependent implements CacheKeyGenerator { + @Inject + ConfigService configService; + + @Override + public Object generate(Method method, Object... methodParams) { + return configService.getPrefix() + methodParams[0]; + } +} + +@RequestScoped +class CompliantRequestScoped implements CacheKeyGenerator { + @Inject + ConfigService configService; + + @Override + public Object generate(Method method, Object... methodParams) { + return configService.getPrefix() + methodParams[0]; + } +} + +class CompliantImplicitConstructor implements CacheKeyGenerator { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + +class CompliantExplicitNoArgsConstructor implements CacheKeyGenerator { + public CompliantExplicitNoArgsConstructor() {} + + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + +class CompliantMultipleConstructorsWithNoArgs implements CacheKeyGenerator { + private final String prefix; + + public CompliantMultipleConstructorsWithNoArgs() { + this.prefix = "default"; + } + + public CompliantMultipleConstructorsWithNoArgs(String prefix) { + this.prefix = prefix; + } + + @Override + public Object generate(Method method, Object... methodParams) { + return prefix + methodParams[0]; + } +} + +abstract class CompliantAbstractClass implements CacheKeyGenerator { +} + +interface CompliantInterface extends CacheKeyGenerator { +} + +class NotACacheKeyGenerator { + public NotACacheKeyGenerator(String param) {} +} + +class ConfigService { + public String getPrefix() { + return "prefix"; + } +} + +class DatabaseService { + public String format(Object obj) { + return obj.toString(); + } +} + +class CacheConfig { +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java new file mode 100644 index 00000000000..ae3a49a29cf --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -0,0 +1,99 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks.quarkus; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S8909") +public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisitor { + + private static final String CACHE_KEY_GENERATOR = "io.quarkus.cache.CacheKeyGenerator"; + + private static final List CDI_SCOPE_ANNOTATIONS = Arrays.asList( + "jakarta.enterprise.context.ApplicationScoped", + "jakarta.enterprise.context.Dependent", + "jakarta.enterprise.context.RequestScoped", + "jakarta.enterprise.context.SessionScoped", + "jakarta.enterprise.context.ConversationScoped", + "javax.enterprise.context.ApplicationScoped", + "javax.enterprise.context.Dependent", + "javax.enterprise.context.RequestScoped", + "javax.enterprise.context.SessionScoped", + "javax.enterprise.context.ConversationScoped" + ); + + @Override + public List nodesToVisit() { + return Collections.singletonList(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + ClassTree classTree = (ClassTree) tree; + if (isApplicableClass(classTree) && !hasCdiScopeAnnotation(classTree) && !hasPublicNoArgsConstructor(classTree)) { + reportIssue(Objects.requireNonNull(classTree.simpleName()), + "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor."); + } + } + + private static boolean isApplicableClass(ClassTree classTree) { + return !isAnonymous(classTree) + && !classTree.symbol().isAbstract() + && !classTree.symbol().isInterface() + && implementsCacheKeyGenerator(classTree); + } + + private static boolean isAnonymous(ClassTree classTree) { + return classTree.simpleName() == null; + } + + private static boolean implementsCacheKeyGenerator(ClassTree classTree) { + return classTree.symbol().type().isSubtypeOf(CACHE_KEY_GENERATOR); + } + + private static boolean hasCdiScopeAnnotation(ClassTree classTree) { + return CDI_SCOPE_ANNOTATIONS.stream() + .anyMatch(annotation -> classTree.symbol().metadata().isAnnotatedWith(annotation)); + } + + private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { + Collection constructors = classTree.symbol().lookupSymbols(""); + var noArgConstructor = constructors.stream() + .filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor) + .findFirst(); + + if (noArgConstructor.isEmpty()) { + return false; + } + + // Constructor is public, or it's implicit (no explicit declaration) + return noArgConstructor.get().isPublic() || noArgConstructor.get().declaration() == null; + } + + private static boolean isNoArgConstructor(Symbol constructor) { + return ((Symbol.MethodSymbol) constructor).parameterTypes().isEmpty(); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java new file mode 100644 index 00000000000..c4202d2af33 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java @@ -0,0 +1,33 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks.quarkus; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class CacheKeyGeneratorInstantiableCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java")) + .withCheck(new CacheKeyGeneratorInstantiableCheck()) + .verifyIssues(); + } +} From b60c6f508cb4aa8ecea9db4d2340155db07ad951 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 08:26:33 +0200 Subject: [PATCH 02/22] SONARJAVA-6489 Fix S8909 implementation and tests - Fixed CacheKeyGeneratorInstantiableCheck implementation to properly detect issues - Added mock-up CacheKeyGenerator interface for testing instead of external dependency - Moved test sample to compiling sources with proper mock-ups - Test now passes successfully Co-Authored-By: Claude Sonnet 4.5 --- .../main/java/io/quarkus/cache/CacheKeyGenerator.java | 10 ++++++++++ .../quarkus/CacheKeyGeneratorInstantiableCheck.java | 10 +++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java diff --git a/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java b/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java new file mode 100644 index 00000000000..ff29f8fedfa --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java @@ -0,0 +1,10 @@ +package io.quarkus.cache; + +import java.lang.reflect.Method; + +/** + * Mock-up interface for Quarkus CacheKeyGenerator for testing purposes + */ +public interface CacheKeyGenerator { + Object generate(Method method, Object... methodParams); +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index ae3a49a29cf..d14a1ccaaba 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -53,10 +53,14 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { ClassTree classTree = (ClassTree) tree; - if (isApplicableClass(classTree) && !hasCdiScopeAnnotation(classTree) && !hasPublicNoArgsConstructor(classTree)) { - reportIssue(Objects.requireNonNull(classTree.simpleName()), - "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor."); + if (!isApplicableClass(classTree)) { + return; } + if (hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) { + return; + } + reportIssue(Objects.requireNonNull(classTree.simpleName()), + "Make this class a CDI bean by adding a scope annotation, or add a public no-args constructor."); } private static boolean isApplicableClass(ClassTree classTree) { From f5281da9f552bf2a2ad13e72ac387c0a95af88fe Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 09:40:37 +0200 Subject: [PATCH 03/22] SONARJAVA-6489 Add S8909 rule metadata and documentation - Generated HTML and JSON rule metadata from RSPEC - Added S8909 to Sonar way profile - Rule detects non-instantiable CacheKeyGenerator implementations Co-Authored-By: Claude Sonnet 4.5 --- .../org/sonar/l10n/java/rules/java/S8909.html | 82 +++++++++++++++++++ .../org/sonar/l10n/java/rules/java/S8909.json | 25 ++++++ .../java/rules/java/Sonar_way_profile.json | 3 +- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html new file mode 100644 index 00000000000..789705e1d01 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.html @@ -0,0 +1,82 @@ +

This rule raises an issue when a class implements the cache key generator interface but does not provide a mechanism for the caching framework to +instantiate it, either through a default constructor or through registration with the dependency injection container.

+

In Java, this specifically refers to the CacheKeyGenerator interface and CDI (Contexts and Dependency Injection) bean annotations.

+

Why is this an issue?

+

When implementing a custom cache key generator interface in the framework, the caching subsystem must be able to create instances of your +implementation class. The framework supports two instantiation mechanisms:

+
    +
  • Managed component injection: If your class is registered as a managed component in the dependency injection container + (annotated with scope management annotations that mark it as application-scoped, request-scoped, dependent-scoped, etc.), the framework will use the + container to create and manage instances.
  • +
  • Public no-args constructor: If your class is not a managed component, the framework will create new instances using reflection + by calling a public no-args constructor.
  • +
+

When a cache key generator implementation lacks both of these, the framework cannot instantiate it. This prevents the cache from generating keys +properly.

+

The problem typically manifests when developers try to pass configuration or dependencies through constructor parameters without providing an +alternative instantiation path. While the intention may be to configure the key generator, this approach breaks the framework’s ability to create +instances.

+

What happens at runtime

+

When the cache attempts to use your key generator, the framework will try to instantiate it. Without a valid instantiation mechanism:

+
    +
  • The framework cannot create an instance of your generator
  • +
  • A runtime exception will be thrown when cache operations occur
  • +
  • Cache annotations that reference your generator will fail
  • +
+

This failure occurs during actual cache operations, not at application startup, making it harder to detect during development.

+

What is the potential impact?

+

When custom cache key generator implementations cannot be instantiated, the application will fail at runtime when cache operations attempt to use +the key generator. This breaks caching functionality for any methods that reference the problematic generator.

+

Users will experience:

+
    +
  • Failed cache lookups and invalidations
  • +
  • Broken cache-related functionality, potentially affecting application performance if caching is critical
  • +
  • Possible application crashes if exceptions are not properly handled
  • +
  • Degraded performance if the caching layer becomes unavailable
  • +
  • Errors that only appear during actual usage, making them harder to detect during development
  • +
+

Since the failure happens at runtime rather than build time, it may not be caught during development and could reach production environments.

+

How to fix it in Quarkus

+

Convert your CacheKeyGenerator implementation into a CDI bean by adding a scope annotation like @ApplicationScoped or +@Dependent. This allows you to inject dependencies normally while letting Quarkus manage the instance lifecycle.

+

Code examples

+

Noncompliant code example

+
+public class CustomKeyGen implements CacheKeyGenerator {
+    private final ConfigService configService;
+
+    public CustomKeyGen(ConfigService configService) { // Noncompliant
+        this.configService = configService;
+    }
+
+    @Override
+    public Object generate(Method method, Object... methodParams) {
+        String prefix = configService.getPrefix();
+        return new CompositeCacheKey(prefix, methodParams[0]);
+    }
+}
+
+

Compliant solution

+
+@ApplicationScoped
+public class CustomKeyGen implements CacheKeyGenerator {
+    @Inject
+    ConfigService configService;
+
+    @Override
+    public Object generate(Method method, Object... methodParams) {
+        String prefix = configService.getPrefix();
+        return new CompositeCacheKey(prefix, methodParams[0]);
+    }
+}
+
+

Resources

+ +

Related rules

+
    +
  • {rule:java:S2060} - Classes should not be coupled to their subclasses through constructors or static methods
  • +
+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json new file mode 100644 index 00000000000..6293081c0ca --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8909.json @@ -0,0 +1,25 @@ +{ + "title": "\"CacheKeyGenerator\" implementations should be instantiable by the framework", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "quarkus", + "injection", + "pitfall" + ], + "defaultSeverity": "Critical", + "ruleSpecification": "RSPEC-8909", + "sqKey": "S8909", + "scope": "Main", + "quickfix": "unknown", + "code": { + "impacts": { + "RELIABILITY": "HIGH" + }, + "attribute": "COMPLETE" + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 9c58ec16ec8..1ced4f11c91 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -531,6 +531,7 @@ "S8714", "S8715", "S8745", - "S8786" + "S8786", + "S8909" ] } From 84e8774394c048a974ebdf3ff505d5acbbc18ba4 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 10:32:29 +0200 Subject: [PATCH 04/22] Address PR comment from gitar-bot[bot] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comment:
Code Review ⚠️ Changes requested 1 resolved / 3 findings Implements rule S8909 for Quarkus CacheKeyGenerator instantiability, but introduces a redundant dependency conflict and incorrectly evaluates implicit package-private constructors as public.
⚠️ Quality: Redundant quarkus-cache dependency conflicts with local mock interface 📄 java-checks-test-sources/default/pom.xml:189-194 📄 java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java:8-9 The PR adds the real `io.quarkus:quarkus-cache:3.2.0.Final` dependency in pom.xml AND also adds a hand-written mock `io/quarkus/cache/CacheKeyGenerator.java` with the same fully-qualified name. The PR description states the mock was created "avoiding external dependency", which directly contradicts also adding the dependency. Having both a local source file and a jar that define `io.quarkus.cache.CacheKeyGenerator` on the same compile path is at best redundant and can cause type-resolution ambiguity / duplicate-type issues in the test-sources module. Pick one approach: either rely on the real dependency and delete the mock, or keep the mock and drop the new dependency from pom.xml (consistent with the stated intent). Note the real Quarkus `CacheKeyGenerator` also declares a default `boolean generationGroup()` method, so the mock is not a faithful copy.
💡 Edge Case: Implicit constructor of package-private class treated as public 📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:86-98 📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:49-51 `hasPublicNoArgsConstructor` returns true when the no-arg constructor's `declaration() == null` (implicit default constructor), regardless of visibility. An implicit default constructor inherits the class's access level, so for a package-private (or private nested) class implementing `CacheKeyGenerator` the implicit constructor is NOT public. The issue message explicitly tells users to add a "public no-args constructor", yet such a class is reported compliant — an inconsistency that can produce a false negative if Quarkus requires a public constructor for non-CDI instantiation. Consider also checking the effective visibility of the implicit constructor (e.g. require the class itself to be public), or align the message with the actual check. Also note records/enums implementing the interface are not covered since `nodesToVisit()` only registers `Tree.Kind.CLASS`.
✅ 1 resolved
Quality: Missing S8909 rule metadata (HTML + JSON) breaks build > 📄 java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:30 > The new rule `CacheKeyGeneratorInstantiableCheck` is annotated with `@Rule(key = "S8909")` and is auto-registered via `CheckListGenerator`, but no metadata files exist for it. `GeneratedCheckListTest` asserts that both `org/sonar/l10n/java/rules/java/S8909.html` and `S8909.json` exist for every registered rule (see GeneratedCheckListTest.java:137-142), and a Glob confirms neither file is present. Without these the rule has no description, no severity/type/profile assignment, and the test suite (hence the build) will fail. Add `S8909.html` (rule description) and `S8909.json` (metadata: title, type, status, tags, remediation, default severity, and SonarWay profile membership) under `java-checks/src/main/resources/org/sonar/l10n/java/rules/java/`.
🤖 Prompt for agents ```` Code Review: Implements rule S8909 for Quarkus CacheKeyGenerator instantiability, but introduces a redundant dependency conflict and incorrectly evaluates implicit package-private constructors as public. 1. ⚠️ Quality: Redundant quarkus-cache dependency conflicts with local mock interface Files: java-checks-test-sources/default/pom.xml:189-194, java-checks-test-sources/default/src/main/java/io/quarkus/cache/CacheKeyGenerator.java:8-9 The PR adds the real `io.quarkus:quarkus-cache:3.2.0.Final` dependency in pom.xml AND also adds a hand-written mock `io/quarkus/cache/CacheKeyGenerator.java` with the same fully-qualified name. The PR description states the mock was created "avoiding external dependency", which directly contradicts also adding the dependency. Having both a local source file and a jar that define `io.quarkus.cache.CacheKeyGenerator` on the same compile path is at best redundant and can cause type-resolution ambiguity / duplicate-type issues in the test-sources module. Pick one approach: either rely on the real dependency and delete the mock, or keep the mock and drop the new dependency from pom.xml (consistent with the stated intent). Note the real Quarkus `CacheKeyGenerator` also declares a default `boolean generationGroup()` method, so the mock is not a faithful copy. 2. 💡 Edge Case: Implicit constructor of package-private class treated as public Files: java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:86-98, java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java:49-51 `hasPublicNoArgsConstructor` returns true when the no-arg constructor's `declaration() == null` (implicit default constructor), regardless of visibility. An implicit default constructor inherits the class's access level, so for a package-private (or private nested) class implementing `CacheKeyGenerator` the implicit constructor is NOT public. The issue message explicitly tells users to add a "public no-args constructor", yet such a class is reported compliant — an inconsistency that can produce a false negative if Quarkus requires a public constructor for non-CDI instantiation. Consider also checking the effective visibility of the implicit constructor (e.g. require the class itself to be public), or align the message with the actual check. Also note records/enums implementing the interface are not covered since `nodesToVisit()` only registers `Tree.Kind.CLASS`. ````
Options Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.
Unblock → Override a blocking verdict and allow merging. Comment with these commands to change:
Auto-apply Compact Unblock
``` gitar auto-apply:on ``` ``` gitar display:verbose ``` ``` gitar unblock ```
Was this helpful? React with 👍 / 👎 | [Gitar](https://gitar.ai) --- java-checks-test-sources/default/pom.xml | 6 ------ .../quarkus/CacheKeyGeneratorInstantiableCheckSample.java | 3 ++- .../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 7 +++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index b14d9092072..0513451a9c3 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -186,12 +186,6 @@ jar provided
- - io.quarkus - quarkus-cache - 3.2.0.Final - provided - io.reactivex rxjava diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index 8eeedecb5f8..e06a3ec0acc 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -99,7 +99,8 @@ public Object generate(Method method, Object... methodParams) { } } -class CompliantImplicitConstructor implements CacheKeyGenerator { +class NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @Override public Object generate(Method method, Object... methodParams) { return methodParams[0]; diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index d14a1ccaaba..f0efe954818 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -93,8 +93,11 @@ private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { return false; } - // Constructor is public, or it's implicit (no explicit declaration) - return noArgConstructor.get().isPublic() || noArgConstructor.get().declaration() == null; + Symbol constructor = noArgConstructor.get(); + if (constructor.declaration() == null) { + return classTree.symbol().isPublic(); + } + return constructor.isPublic(); } private static boolean isNoArgConstructor(Symbol constructor) { From 47f04a74397adf11ac4e9962b493ca9372684770 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 13:21:22 +0200 Subject: [PATCH 05/22] Fix Windows CI failure - Fix Noncompliant marker length in CacheKeyGeneratorInstantiableCheckSample (50 carets -> 45 carets to match class name) --- .../quarkus/CacheKeyGeneratorInstantiableCheckSample.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index e06a3ec0acc..648ef47b9a8 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -100,7 +100,7 @@ public Object generate(Method method, Object... methodParams) { } class NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @Override public Object generate(Method method, Object... methodParams) { return methodParams[0]; From 35b482e2f5bfd98c5b8a94d4094a435ec50dd96d Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 16:04:58 +0200 Subject: [PATCH 06/22] Update autoscan expected test results for S8909 Add expected autoscan diff for new rule S8909 (3 false positives in autoscan mode) and update S6813 false negatives count (65 -> 68). Update test to expect 11 rules causing FPs instead of 10. Co-Authored-By: Claude Sonnet 4.5 --- .../src/test/java/org/sonar/java/it/AutoScanTest.java | 2 +- .../src/test/resources/autoscan/diffs/diff_S6813.json | 4 ++-- .../src/test/resources/autoscan/diffs/diff_S8909.json | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java index ad179fb3db2..fde5893c34a 100644 --- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java +++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java @@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception { SoftAssertions softly = new SoftAssertions(); softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values()); softly.assertThat(newTotal).isEqualTo(knownTotal); - softly.assertThat(rulesCausingFPs).hasSize(10); + softly.assertThat(rulesCausingFPs).hasSize(11); softly.assertThat(rulesNotReporting).hasSize(19); /** diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json index d50149014fc..59ff1971854 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json @@ -1,6 +1,6 @@ { "ruleKey": "S6813", "hasTruePositives": true, - "falseNegatives": 65, + "falseNegatives": 68, "falsePositives": 0 -} +} \ No newline at end of file diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json new file mode 100644 index 00000000000..1f1849fcc1b --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S8909", + "hasTruePositives": true, + "falseNegatives": 0, + "falsePositives": 3 +} \ No newline at end of file From 841ff2a20ac05e2df3dc559054022b29267ed1a6 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Thu, 25 Jun 2026 10:28:38 +0200 Subject: [PATCH 07/22] Fix CI: Updated hardcoded count in AutoScanTest.java from 11 to 12 to account for rule S8909 causing false positives --- its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java index bfae131a57a..7b306554c4d 100644 --- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java +++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java @@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception { SoftAssertions softly = new SoftAssertions(); softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values()); softly.assertThat(newTotal).isEqualTo(knownTotal); - softly.assertThat(rulesCausingFPs).hasSize(11); + softly.assertThat(rulesCausingFPs).hasSize(12); softly.assertThat(rulesNotReporting).hasSize(19); /** From 413d87dfdaf7ebf1f1bf8b2426f945686d0ee1c3 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Thu, 25 Jun 2026 16:39:16 +0200 Subject: [PATCH 08/22] Fix CI: Updated diff_S6813.json to reflect 3 additional false negatives (66 -> 69) --- its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json index a9b3acaf13a..a0a3f51e87b 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json @@ -1,6 +1,6 @@ { "ruleKey": "S6813", "hasTruePositives": true, - "falseNegatives": 66, + "falseNegatives": 69, "falsePositives": 0 } \ No newline at end of file From f1356d83c02a4aa13468638832144ca506e23f6c Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 29 Jun 2026 15:47:13 +0200 Subject: [PATCH 09/22] Address review comment from rombirli on java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java Comment: List.of --- .../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index f0efe954818..a4fabe88dea 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -16,7 +16,6 @@ */ package org.sonar.java.checks.quarkus; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,7 +31,7 @@ public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisi private static final String CACHE_KEY_GENERATOR = "io.quarkus.cache.CacheKeyGenerator"; - private static final List CDI_SCOPE_ANNOTATIONS = Arrays.asList( + private static final List CDI_SCOPE_ANNOTATIONS = List.of( "jakarta.enterprise.context.ApplicationScoped", "jakarta.enterprise.context.Dependent", "jakarta.enterprise.context.RequestScoped", From 5ed36099ef2710bdd09e19b7e4742fabd5f0f143 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 29 Jun 2026 15:48:32 +0200 Subject: [PATCH 10/22] Address review comment from rombirli on java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java Comment: List.of --- .../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index a4fabe88dea..789b26d72b0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -17,7 +17,6 @@ package org.sonar.java.checks.quarkus; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Objects; import org.sonar.check.Rule; @@ -46,7 +45,7 @@ public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisi @Override public List nodesToVisit() { - return Collections.singletonList(Tree.Kind.CLASS); + return List.of(Tree.Kind.CLASS); } @Override From 46ffe7f445cc41e656b1e328457b74ecf232d399 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 29 Jun 2026 15:49:40 +0200 Subject: [PATCH 11/22] Address review comment from rombirli on java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java Comment: Why dont we merge those `if`s --- .../checks/quarkus/CacheKeyGeneratorInstantiableCheck.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index 789b26d72b0..44b0965e207 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -51,10 +51,7 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { ClassTree classTree = (ClassTree) tree; - if (!isApplicableClass(classTree)) { - return; - } - if (hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) { + if (!isApplicableClass(classTree) || hasCdiScopeAnnotation(classTree) || hasPublicNoArgsConstructor(classTree)) { return; } reportIssue(Objects.requireNonNull(classTree.simpleName()), From d1273ff66e6a080f0717e4152593dd4fc8e50707 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 29 Jun 2026 15:50:42 +0200 Subject: [PATCH 12/22] Address review comment from rombirli on java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java Comment: A bit verbose, I propose ```suggestion private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { Collection constructors = classTree.symbol().lookupSymbols(""); return constructors.stream() .map(Symbol.MethodSymbol.class::cast) .filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor) .findFirst() .map(Symbol::isPublic) .orElse(classTree.symbol().isPublic()); } private static boolean isNoArgConstructor(Symbol.MethodSymbol constructor) { return constructor.parameterTypes().isEmpty(); } ``` --- .../CacheKeyGeneratorInstantiableCheck.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index 44b0965e207..5dd1587f776 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -80,22 +80,15 @@ private static boolean hasCdiScopeAnnotation(ClassTree classTree) { private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { Collection constructors = classTree.symbol().lookupSymbols(""); - var noArgConstructor = constructors.stream() + return constructors.stream() + .map(Symbol.MethodSymbol.class::cast) .filter(CacheKeyGeneratorInstantiableCheck::isNoArgConstructor) - .findFirst(); - - if (noArgConstructor.isEmpty()) { - return false; - } - - Symbol constructor = noArgConstructor.get(); - if (constructor.declaration() == null) { - return classTree.symbol().isPublic(); - } - return constructor.isPublic(); + .findFirst() + .map(Symbol::isPublic) + .orElse(classTree.symbol().isPublic()); } - private static boolean isNoArgConstructor(Symbol constructor) { - return ((Symbol.MethodSymbol) constructor).parameterTypes().isEmpty(); + private static boolean isNoArgConstructor(Symbol.MethodSymbol constructor) { + return constructor.parameterTypes().isEmpty(); } } From 72490f1dd23f44d8d70497ead72b4606a5785a8c Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 29 Jun 2026 16:29:40 +0200 Subject: [PATCH 13/22] Fix S8909 autoscan false positives --- .../java/org/sonar/java/it/AutoScanTest.java | 2 +- .../resources/autoscan/diffs/diff_S8909.json | 2 +- .../CacheKeyGeneratorInstantiableCheck.java | 21 +++++++++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java index 7b306554c4d..bfae131a57a 100644 --- a/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java +++ b/its/autoscan/src/test/java/org/sonar/java/it/AutoScanTest.java @@ -198,7 +198,7 @@ public void javaCheckTestSources() throws Exception { SoftAssertions softly = new SoftAssertions(); softly.assertThat(newDiffs).containsExactlyInAnyOrderElementsOf(knownDiffs.values()); softly.assertThat(newTotal).isEqualTo(knownTotal); - softly.assertThat(rulesCausingFPs).hasSize(12); + softly.assertThat(rulesCausingFPs).hasSize(11); softly.assertThat(rulesNotReporting).hasSize(19); /** diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json index 1f1849fcc1b..f5fab362944 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json @@ -2,5 +2,5 @@ "ruleKey": "S8909", "hasTruePositives": true, "falseNegatives": 0, - "falsePositives": 3 + "falsePositives": 0 } \ No newline at end of file diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index 5dd1587f776..a3a518039e0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -22,7 +22,10 @@ import org.sonar.check.Rule; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.AnnotationTree; import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.Tree; @Rule(key = "S8909") @@ -74,8 +77,22 @@ private static boolean implementsCacheKeyGenerator(ClassTree classTree) { } private static boolean hasCdiScopeAnnotation(ClassTree classTree) { - return CDI_SCOPE_ANNOTATIONS.stream() - .anyMatch(annotation -> classTree.symbol().metadata().isAnnotatedWith(annotation)); + return CDI_SCOPE_ANNOTATIONS.stream().anyMatch(annotation -> + classTree.symbol().metadata().isAnnotatedWith(annotation) + || classTree.modifiers().annotations().stream().anyMatch(tree -> matchesAnnotation(tree, annotation))); + } + + private static boolean matchesAnnotation(AnnotationTree annotationTree, String fullyQualifiedName) { + String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1); + Tree annotationType = annotationTree.annotationType(); + return switch (annotationType.kind()) { + case IDENTIFIER -> ((IdentifierTree) annotationType).name().equals(simpleName); + case MEMBER_SELECT -> { + MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType; + yield memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName); + } + default -> false; + }; } private static boolean hasPublicNoArgsConstructor(ClassTree classTree) { From 5989e84f1f8e646300a7132f7ec82f6614ddf8d3 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 30 Jun 2026 14:12:19 +0200 Subject: [PATCH 14/22] Add test coverage for S8909 matchesAnnotation MEMBER_SELECT branch Add compliant test cases using fully qualified CDI annotations (@jakarta.enterprise.context.ApplicationScoped and @javax.enterprise.context.ApplicationScoped) and a withoutSemantic test to exercise the AST-based annotation matching fallback path. Co-Authored-By: Claude Opus 4.6 --- ...CacheKeyGeneratorInstantiableCheckSample.java | 16 ++++++++++++++++ .../CacheKeyGeneratorInstantiableCheckTest.java | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index 648ef47b9a8..a060560e5be 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -133,6 +133,22 @@ public Object generate(Method method, Object... methodParams) { } } +@jakarta.enterprise.context.ApplicationScoped +class CompliantFullyQualifiedJakartaAnnotation implements CacheKeyGenerator { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + +@javax.enterprise.context.ApplicationScoped +class CompliantFullyQualifiedJavaxAnnotation implements CacheKeyGenerator { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + abstract class CompliantAbstractClass implements CacheKeyGenerator { } diff --git a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java index c4202d2af33..a4f0dba2f57 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java @@ -30,4 +30,13 @@ void test() { .withCheck(new CacheKeyGeneratorInstantiableCheck()) .verifyIssues(); } + + @Test + void testWithoutSemantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java")) + .withCheck(new CacheKeyGeneratorInstantiableCheck()) + .withoutSemantic() + .verifyNoIssues(); + } } From 793eba7182c128260e3cba93abd5da568474c2b9 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 29 Jun 2026 16:23:31 +0200 Subject: [PATCH 15/22] SONARJAVA-6536 Make sonar-java required for Kotlin (#5711) --- sonar-java-plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml index f7229dba8f0..278a2c704bb 100644 --- a/sonar-java-plugin/pom.xml +++ b/sonar-java-plugin/pom.xml @@ -153,7 +153,7 @@ Java Code Quality and Security true org.sonar.plugins.java.JavaPlugin - java,jsp + java,jsp,kotlin true 9.14.0.375 17 From 733bc7f40e8f1bcb5059e11677b0088dc2c2d0ed Mon Sep 17 00:00:00 2001 From: Asya Vorobeva Date: Mon, 29 Jun 2026 21:33:05 +0200 Subject: [PATCH 16/22] AT-24 Declassify two "beta" rules: S6539 and S6548 (#5715) --- .../src/test/resources/autoscan/diffs/diff_S6548.json | 6 ------ .../resources/org/sonar/l10n/java/rules/java/S6539.json | 2 +- .../resources/org/sonar/l10n/java/rules/java/S6548.json | 2 +- .../l10n/java/rules/java/Sonar_agentic_AI_profile.json | 1 - .../org/sonar/l10n/java/rules/java/Sonar_way_profile.json | 1 - .../org/sonar/plugins/java/JavaAgenticWayProfileTest.java | 3 ++- 6 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json deleted file mode 100644 index 4cb9dce7c70..00000000000 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ruleKey": "S6548", - "hasTruePositives": true, - "falseNegatives": 0, - "falsePositives": 0 -} \ No newline at end of file diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json index be17f30e118..0fc8f397ead 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6539.json @@ -7,7 +7,7 @@ }, "attribute": "MODULAR" }, - "status": "beta", + "status": "ready", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json index 9d2cb3292c3..2bcdbfdc8d3 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6548.json @@ -7,7 +7,7 @@ }, "attribute": "MODULAR" }, - "status": "beta", + "status": "deprecated", "remediation": { "func": "Constant\/Issue", "constantCost": "0min" diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json index 73bf9f71841..0f8b8e56c37 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json @@ -402,7 +402,6 @@ "S6485", "S6539", "S6541", - "S6548", "S6806", "S6809", "S6810", diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json index 8bd063b6d83..823ca6f2864 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json @@ -454,7 +454,6 @@ "S6485", "S6539", "S6541", - "S6548", "S6804", "S6806", "S6809", diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java index 4224106f7f0..16c0b03062b 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -53,7 +53,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(467) + .hasSize(466) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101", @@ -113,6 +113,7 @@ void profile_is_registered_as_expected() { "S6242", "S6244", "S6246", + "S6548", "S6804", "S6813", "S6829", From e03b2118c6442fe9568e4d59e77ac4a43096aee6 Mon Sep 17 00:00:00 2001 From: "hashicorp-vault-sonar-prod[bot]" <111297361+hashicorp-vault-sonar-prod[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:47:56 +0000 Subject: [PATCH 17/22] SONARJAVA-6542 Update rule metadata (#5717) Co-authored-by: asya-vorobeva --- .../org/sonar/l10n/java/rules/java/S1110.json | 24 ++-- .../org/sonar/l10n/java/rules/java/S2206.json | 2 +- .../org/sonar/l10n/java/rules/java/S2654.html | 114 ++++++++++++++++-- .../org/sonar/l10n/java/rules/java/S2654.json | 9 +- .../org/sonar/l10n/java/rules/java/S5542.html | 6 - .../org/sonar/l10n/java/rules/java/S5976.html | 12 +- .../org/sonar/l10n/java/rules/java/S6206.html | 1 + .../org/sonar/l10n/java/rules/java/S8911.html | 33 ++--- .../org/sonar/l10n/java/rules/java/S8924.html | 23 +++- .../org/sonar/l10n/java/rules/java/S8924.json | 4 +- .../rules/java/Sonar_agentic_AI_profile.json | 1 - .../java/JavaAgenticWayProfileTest.java | 5 +- sonarpedia.json | 2 +- 13 files changed, 170 insertions(+), 66 deletions(-) diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json index 2b607b5003f..87cbe8691bb 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S1110.json @@ -1,23 +1,25 @@ { - "title": "Redundant pairs of parentheses should be removed", + "title": "Unnecessary parentheses should be removed", "type": "CODE_SMELL", - "code": { - "impacts": { - "MAINTAINABILITY": "MEDIUM" - }, - "attribute": "CLEAR" - }, "status": "ready", "remediation": { "func": "Constant\/Issue", - "constantCost": "1min" + "constantCost": "2 min" }, "tags": [ - "confusing" + "clippy", + "redundant", + "readability" ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-1110", "sqKey": "S1110", "scope": "All", - "quickfix": "unknown" + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CLEAR" + } } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json index 3e6c2d30eb9..a7c838a29c4 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2206.json @@ -1,5 +1,5 @@ { - "title": "Either fields or getters should be annotated for persistence but not both", + "title": "Do not mix field and getter persistence annotations", "type": "BUG", "status": "ready", "remediation": { diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html index 7f2832d9391..5b6762cbe6c 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.html @@ -1,18 +1,106 @@

Why is this an issue?

-

Proper synchronization and thread management can be tricky under the best of circumstances, but it’s particularly difficult in JEE application, and -is even forbidden under some circumstances by the JEE standard.

-

This rule raises an issue for each Runnable, and use of the synchronized keyword.

-

Noncompliant code example

-
-public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-  // ...
-
-  Runnable r = new Runnable() {  // Noncompliant
-    public void run() {
+

Proper synchronization and thread management can be tricky under the best of circumstances, but it’s particularly difficult in Jakarta EE +application, and is even forbidden under some circumstances by the Jakarta EE standard.

+

This rule raises an issue for each Runnable, and use of the synchronized keyword in Jakarta EE applications.

+

How to fix it

+

Instead of creating unmanaged threads, use the Concurrency Utilities for Jakarta EE. Injecting a ManagedExecutorService lets the +container manage the thread pool and preserves the container context (CDI, security, transactions). If you are using EJBs, annotate a method with +@Asynchronous to run it in a container-managed background thread, with no need to manage threads or submit tasks explicitly.

+

For shared mutable state that would otherwise require synchronized, use container-managed concurrency instead. In EJBs, the container +serializes access to @Singleton beans by default (via @Lock(WRITE)), eliminating the need for explicit synchronization. +Outside EJBs, use the concurrency utilities from java.util.concurrent such as AtomicInteger or +ConcurrentHashMap, which are safe to use in Jakarta EE.

+

Code examples

+

Noncompliant code example

+
+@RequestScoped
+public class DataProcessor {
+
+  public void processData() {
+    Runnable task = () -> { // Noncompliant
+      // ...
+    };
+    new Thread(task).start();
+  }
+}
+
+

Compliant solution

+

Using ManagedExecutorService

+
+@RequestScoped
+public class DataProcessor {
+
+  @Resource
+  private ManagedExecutorService executor;
+
+  public void processData() {
+    executor.submit(() -> { // Compliant
       // ...
-    }
-  };
-  new Thread(r).start();
+    });
+  }
+}
+
+

Noncompliant code example

+
+@Stateless
+public class OrderService {
+
+  public void dispatchOrder(Long orderId) {
+    Runnable r = new Runnable() { // Noncompliant
+      public void run() {
+        // ...
+      }
+    };
+    new Thread(r).start();
+  }
+}
+
+

Compliant solution

+

Using @Asynchronous

+
+@Stateless
+public class OrderService {
+
+  @Asynchronous // Compliant
+  public void dispatchOrder(Long orderId) {
+    // ...
+  }
+}
+
+

Noncompliant code example

+
+@Singleton
+public class CounterService {
+
+  private int count = 0;
+
+  public synchronized void increment() { // Noncompliant
+    count++;
+  }
+
+  public synchronized int getCount() { // Noncompliant
+    return count;
+  }
+}
+
+

Compliant solution

+

Using @Singleton with @Lock(…​)

+
+@Singleton
+public class CounterService {
+
+  private int count = 0;
+
+  @Lock(LockType.WRITE) // Compliant - container manages concurrency
+  public void increment() {
+    count++;
+  }
+
+  @Lock(LockType.READ) // Compliant
+  public int getCount() {
+    return count;
+  }
+}
 

Resources

    diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json index 3e16492b588..010d6d723db 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S2654.json @@ -6,9 +6,16 @@ "func": "Constant\/Issue", "constantCost": "45min" }, + "code": { + "impacts": { + "RELIABILITY": "HIGH" + }, + "attribute": "TRUSTWORTHY" + }, "tags": [ "cwe", "multi-threading", + "jakarta", "jee" ], "defaultSeverity": "Critical", @@ -21,5 +28,5 @@ 574 ] }, - "quickfix": "unknown" + "quickfix": "infeasible" } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html index f6498d2ca9d..affebc7a3fa 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5542.html @@ -16,8 +16,6 @@

    Why is this an issue?

    secure by the cryptography community.

    For AES, the weakest mode is ECB (Electronic Codebook). Repeated blocks of data are encrypted to the same value, making them easy to identify and reducing the difficulty of recovering the original cleartext.

    -

    Unauthenticated modes such as CBC (Cipher Block Chaining) may be used but are prone to attacks that manipulate the ciphertext. They must be used -with caution.

    For RSA, the weakest algorithms are either using it without padding or using the PKCS1v1.5 padding scheme.

    What is the potential impact?

    The cleartext of an encrypted message might be recoverable. Additionally, it might be possible to modify the cleartext of an encrypted message.

    @@ -103,16 +101,12 @@

    For AES: use authenticated encryption modes

  • IAPM: Integer Authenticated Parallelizable Mode
  • OCB: Offset Codebook Mode
-

It is also possible to use AES-CBC with HMAC for integrity checks. However, it is considered more straightforward to use AES-GCM directly -instead.

For RSA: use the OAEP scheme

The Optimal Asymmetric Encryption Padding scheme (OAEP) adds randomness and a secure hash function that strengthens the regular inner workings of RSA.

Resources

Articles & blog posts

    -
  • Microsoft, Timing vulnerabilities with CBC-mode - symmetric decryption using padding
  • Wikipedia, Padding Oracle Attack
  • Wikipedia, Chosen-Ciphertext Attack
  • Wikipedia, Chosen-Plaintext Attack
  • diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html index b717363fe72..414c893af63 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5976.html @@ -1,10 +1,10 @@ +

    This rule raises an issue when at least 3 test methods could be refactored into a single parameterized test with less than 4 parameters.

    Why is this an issue?

    -

    When multiple tests differ only by a few hardcoded values they should be refactored as a single "parameterized" test. This reduces the chances of -adding a bug and makes them more readable. Parameterized tests exist in most test frameworks (JUnit, TestNG, etc…​).

    -

    The right balance needs of course to be found. There is no point in factorizing test methods when the parameterized version is a lot more complex -than initial tests.

    -

    This rule raises an issue when at least 3 tests could be refactored as one parameterized test with less than 4 parameters. Only test methods which -have at least one duplicated statement are considered.

    +

    When multiple tests differ only by a few hardcoded values, they should be refactored into a single parameterized test. This reduces duplication, +makes the tests easier to read, and lowers the risk of introducing bugs when the test logic needs to change.

    +

    Parameterized tests are supported by most testing frameworks.

    +

    The right balance still needs to be found. There is little value in parameterizing tests when the resulting test becomes significantly more complex +than the original versions.

    Noncompliant code example

    with JUnit 5

    diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
    index 9b2a70025cd..e8ade5a5fd4 100644
    --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
    +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6206.html
    @@ -80,3 +80,4 @@ 

    Resources

    + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html index 755756ece61..11c6cf1701d 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8911.html @@ -8,23 +8,23 @@

    Why is this an issue?

    When you mark a method for startup execution, the dependency injection framework generates initialization hooks that trigger the method when the application starts. For this mechanism to work correctly, the method must meet three requirements:

      -
    • Non-static: The method must be an instance method. Dependency injection frameworks work by creating managed component - instances and invoking methods on those instances. A static method belongs to the class itself, not to an instance, so the framework cannot properly - manage its lifecycle or invoke it as part of component initialization.
    • -
    • Non-producer: The method cannot be a producer method (one that creates and provides instances for other components to - consume). Producer methods are designed to create and provide component instances, not to perform initialization logic. Combining startup execution - markers with producer functionality creates a conflict in the intended purpose of the method.
    • -
    • No arguments: The method must not accept any parameters. The framework invokes these methods automatically at startup and has - no mechanism to provide argument values. If a method requires parameters, the framework cannot call it.
    • +
    • Non-static: The method must be an instance method. Dependency injection frameworks work by creating managed component instances + and invoking methods on those instances. A static method belongs to the class itself, not to an instance, so the framework cannot properly manage + its lifecycle or invoke it as part of component initialization.
    • +
    • Non-producer: The method cannot be a producer method (one that creates and provides instances for other components to consume). + Producer methods are designed to create and provide component instances, not to perform initialization logic. Combining startup execution markers + with producer functionality creates a conflict in the intended purpose of the method.
    • +
    • No arguments: The method must not accept any parameters. The framework invokes these methods automatically at startup and has + no mechanism to provide argument values. If a method requires parameters, the framework cannot call it.

    What is the potential impact?

    When methods designated for startup execution don’t follow the required signature, the initialization logic will not execute at application startup. This can lead to:

      -
    • Missing configuration or setup steps that are essential for the application to function correctly
    • -
    • Uninitialized resources or connections that other parts of the application depend on
    • -
    • Runtime errors when the application tries to use components that were supposed to be initialized at startup
    • -
    • Silent failures where the method is simply not called, making the problem difficult to diagnose
    • +
    • Missing configuration or setup steps that are essential for the application to function correctly
    • +
    • Uninitialized resources or connections that other parts of the application depend on
    • +
    • Runtime errors when the application tries to use components that were supposed to be initialized at startup
    • +
    • Silent failures where the method is simply not called, making the problem difficult to diagnose

    How to fix it

    Remove the static modifier from the method to make it an instance method. Ensure the method has no parameters and is not annotated @@ -53,8 +53,9 @@

    Compliant solution

    Resources

    Documentation

    + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html index 8a3d75d7016..fc738acd26a 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.html @@ -1,5 +1,5 @@

    This rule raises an issue when Mockito core methods are called with the Mockito. prefix instead of being statically imported.

    -

    This applies to commonly used Mockito methods such as:

    +

    This applies to the following Mockito methods:

    • mock()
    • when()
    • @@ -9,19 +9,19 @@
    • doThrow()
    • times()
    • never()
    • -
    • any()
    • -
    • eq()

    Why is this an issue?

    Mockito was designed with static imports in mind to create a more readable, fluent testing DSL (Domain-Specific Language). When you use the Mockito. prefix for method calls, the test code becomes more verbose and harder to read.

    Compare these two examples:

    -Mockito.when(Mockito.mock(MyService.class).getValue()).thenReturn(42);
    +var myMock = Mockito.mock(MyService.class);
    +Mockito.when(myMock.getValue()).thenReturn(42);
     

    versus:

    -when(mock(MyService.class).getValue()).thenReturn(42);
    +var myMock = mock(MyService.class);
    +when(myMock.getValue()).thenReturn(42);
     

    The second version reads more naturally, almost like plain English, which is the intent of Mockito’s API design. This readability improvement becomes even more significant in larger test files with many mock interactions.

    @@ -51,7 +51,8 @@

    Noncompliant code example

    Compliant solution

    -import static org.mockito.Mockito.*;
    +import static org.mockito.Mockito.when;
    +import static org.mockito.Mockito.verify;
     
     public class MyServiceTest {
         private MyService myService = mock(MyService.class);
    @@ -73,3 +74,13 @@ 

    Documentation

  • Mockito Documentation - How do I import Mockito? - Official Mockito documentation
+
+

Implementation Specification

+

(visible only on this page)

+

Message

+

Use a static import for "{method name}".

+

Highlighting

+
    +
  • Primary: Mockito prefix and method name
  • +
+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json index 18d1e1fc381..4e3239d547c 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8924.json @@ -11,14 +11,14 @@ "tests", "convention" ], - "defaultSeverity": "Major", + "defaultSeverity": "Minor", "ruleSpecification": "RSPEC-8924", "sqKey": "S8924", "scope": "Tests", "quickfix": "targeted", "code": { "impacts": { - "MAINTAINABILITY": "MEDIUM" + "MAINTAINABILITY": "LOW" }, "attribute": "CLEAR" } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json index 0f8b8e56c37..a669258ac60 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_agentic_AI_profile.json @@ -463,7 +463,6 @@ "S8465", "S8469", "S8696", - "S8714", "S8715", "S8745", "S8786" diff --git a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java index 16c0b03062b..5d255874ee4 100644 --- a/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java +++ b/sonar-java-plugin/src/test/java/org/sonar/plugins/java/JavaAgenticWayProfileTest.java @@ -53,7 +53,7 @@ void profile_is_registered_as_expected() { BuiltInQualityProfilesDefinition.BuiltInQualityProfile actualProfile = profilesPerLanguages.get("java").get("Sonar agentic AI"); assertThat(actualProfile.isDefault()).isFalse(); assertThat(actualProfile.rules()) - .hasSize(466) + .hasSize(465) .extracting(BuiltInQualityProfilesDefinition.BuiltInActiveRule::ruleKey) .doesNotContainAnyElementsOf(List.of( "S101", @@ -123,7 +123,8 @@ void profile_is_registered_as_expected() { "S6837", "S6912", "S8491", - "S8692" + "S8692", + "S8714" )); } diff --git a/sonarpedia.json b/sonarpedia.json index ce2fddcc2c0..39cc72ede1b 100644 --- a/sonarpedia.json +++ b/sonarpedia.json @@ -3,7 +3,7 @@ "languages": [ "JAVA" ], - "latest-update": "2026-06-18T09:33:18.987005213Z", + "latest-update": "2026-06-30T10:26:48.191927160Z", "options": { "no-language-in-filenames": true, "preserve-filenames": false From 023097057453e5ce40cd8134b150d37ffb9bcfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Tue, 30 Jun 2026 14:18:06 +0200 Subject: [PATCH 18/22] CLP-85 Update notifications Slack channels (#5693) --- .github/workflows/ToggleLockBranch.yml | 2 +- .github/workflows/automated-release.yml | 2 +- .github/workflows/dogfood.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ToggleLockBranch.yml b/.github/workflows/ToggleLockBranch.yml index 38a88306530..5185bf00f4c 100644 --- a/.github/workflows/ToggleLockBranch.yml +++ b/.github/workflows/ToggleLockBranch.yml @@ -25,4 +25,4 @@ jobs: github-token: ${{ fromJSON(steps.secrets.outputs.vault).lock_token }} slack-token: ${{ fromJSON(steps.secrets.outputs.vault).slack_api_token }} additional-message: ${{ github.event.inputs.additional-message }} - slack-channel: squad-jvm-notifs + slack-channel: squad-corelang-releases diff --git a/.github/workflows/automated-release.yml b/.github/workflows/automated-release.yml index f346fe5994f..95a693949ed 100644 --- a/.github/workflows/automated-release.yml +++ b/.github/workflows/automated-release.yml @@ -68,7 +68,7 @@ jobs: create-slvscode-ticket: ${{ github.event.inputs.ide-integration == 'true' }} branch: ${{ github.event.inputs.branch }} pm-email: "jean.jimbo@sonarsource.com" - slack-channel: "squad-jvm-releases" + slack-channel: "squad-corelang-releases" verbose: ${{ github.event.inputs.verbose == 'true' }} use-jira-sandbox: ${{ github.event.inputs.dry-run == 'true' }} is-draft-release: ${{ github.event.inputs.dry-run == 'true' }} diff --git a/.github/workflows/dogfood.yml b/.github/workflows/dogfood.yml index 1379e1997d0..c08fa0f0c44 100644 --- a/.github/workflows/dogfood.yml +++ b/.github/workflows/dogfood.yml @@ -41,5 +41,5 @@ jobs: env: SLACK_BOT_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).SLACK_BOT_TOKEN }} with: - channel-id: squad-jvm-notifs + channel-id: squad-corelang-notifs slack-message: "Dogfood build for `${{ steps.dogfood.outputs.sha1 }}`: *failed*, see the logs at https://github.com/SonarSource/sonar-java/actions/workflows/dogfood.yml" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 64d924e6349..b2ad46c6cc6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,6 @@ jobs: with: publishToBinaries: true mavenCentralSync: true - slackChannel: squad-jvm-releases + slackChannel: squad-corelang-releases version: ${{ inputs.version }} dryRun: ${{ inputs.dryRun == true }} From 46d4ef07ab85b2c1342c868d8a83c080879aec85 Mon Sep 17 00:00:00 2001 From: Asya Vorobeva Date: Tue, 30 Jun 2026 15:59:13 +0200 Subject: [PATCH 19/22] SONARJAVA-6543 Fix failing SQ quality gate (#5718) --- .../src/test/java/org/sonar/java/model/JParserTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java index e958232839e..1ea02bab66b 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java @@ -53,7 +53,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InOrder; -import org.mockito.Mockito; import org.slf4j.event.Level; import org.sonar.api.batch.fs.InputFile; import org.sonar.java.AnalysisProgress; @@ -93,6 +92,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -189,8 +189,7 @@ void consumer_should_receive_exceptions_thrown_during_parsing() throws Exception InputFile inputFile = mock(InputFile.class); doReturn("/tmp/Example.java") .when(inputFile).absolutePath(); - Mockito - .doThrow(IOException.class) + doThrow(IOException.class) .when(inputFile).contents(); FILE_BY_FILE From dcff4d123c62b6178107e3f96f5b69bd4a68a41f Mon Sep 17 00:00:00 2001 From: "hashicorp-vault-sonar-prod[bot]" <111297361+hashicorp-vault-sonar-prod[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:53:36 +0000 Subject: [PATCH 20/22] SONARJAVA-6545 Prepare next development iteration 8.35.0 (#5720) Co-authored-by: asya-vorobeva --- check-list/pom.xml | 2 +- docs/java-custom-rules-example/pom.xml | 2 +- docs/pom.xml | 2 +- external-reports/pom.xml | 2 +- its/autoscan/pom.xml | 2 +- its/plugin/plugins/java-extension-plugin/pom.xml | 2 +- its/plugin/plugins/pom.xml | 2 +- its/plugin/pom.xml | 2 +- its/plugin/tests/pom.xml | 2 +- its/pom.xml | 2 +- its/ruling/pom.xml | 2 +- java-checks-aws/pom.xml | 2 +- java-checks-common/pom.xml | 2 +- java-checks-test-sources/aws/pom.xml | 2 +- java-checks-test-sources/default/pom.xml | 2 +- java-checks-test-sources/java-17/pom.xml | 2 +- java-checks-test-sources/pom.xml | 2 +- java-checks-test-sources/spring-3.2/pom.xml | 2 +- java-checks-test-sources/spring-web-4.0/pom.xml | 2 +- java-checks-test-sources/test-classpath-reader/pom.xml | 2 +- java-checks-testkit/pom.xml | 2 +- java-checks/pom.xml | 2 +- java-frontend/pom.xml | 2 +- java-jsp/pom.xml | 2 +- java-surefire/pom.xml | 2 +- pom.xml | 2 +- sonar-java-plugin/pom.xml | 2 +- 27 files changed, 27 insertions(+), 27 deletions(-) diff --git a/check-list/pom.xml b/check-list/pom.xml index 0373e5a1a88..4a7cf0ee478 100644 --- a/check-list/pom.xml +++ b/check-list/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT check-list diff --git a/docs/java-custom-rules-example/pom.xml b/docs/java-custom-rules-example/pom.xml index 66aba904346..ead9d69f665 100644 --- a/docs/java-custom-rules-example/pom.xml +++ b/docs/java-custom-rules-example/pom.xml @@ -4,7 +4,7 @@ org.sonarsource.java docs - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT diff --git a/docs/pom.xml b/docs/pom.xml index d353eace3cd..68a8d79660f 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT docs diff --git a/external-reports/pom.xml b/external-reports/pom.xml index d5c0d045d17..805c30cad10 100644 --- a/external-reports/pom.xml +++ b/external-reports/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT external-reports diff --git a/its/autoscan/pom.xml b/its/autoscan/pom.xml index 9b69a7d5fe7..2217cad088c 100644 --- a/its/autoscan/pom.xml +++ b/its/autoscan/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.java java-its - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT it-java-autoscan diff --git a/its/plugin/plugins/java-extension-plugin/pom.xml b/its/plugin/plugins/java-extension-plugin/pom.xml index e1796931087..1d863c13870 100644 --- a/its/plugin/plugins/java-extension-plugin/pom.xml +++ b/its/plugin/plugins/java-extension-plugin/pom.xml @@ -4,7 +4,7 @@ org.sonarsource.java it-java-plugin-plugins - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-extension-plugin diff --git a/its/plugin/plugins/pom.xml b/its/plugin/plugins/pom.xml index 950ffcadca2..a6e6631837f 100644 --- a/its/plugin/plugins/pom.xml +++ b/its/plugin/plugins/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java it-java-plugin - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT it-java-plugin-plugins diff --git a/its/plugin/pom.xml b/its/plugin/pom.xml index 39ded42ea64..b3e01856291 100644 --- a/its/plugin/pom.xml +++ b/its/plugin/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java-its - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT it-java-plugin diff --git a/its/plugin/tests/pom.xml b/its/plugin/tests/pom.xml index 1b987dc7a8a..08e2dae33d9 100644 --- a/its/plugin/tests/pom.xml +++ b/its/plugin/tests/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java it-java-plugin - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT it-java-plugin-tests diff --git a/its/pom.xml b/its/pom.xml index bea3b5fad11..67799fedcd4 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-its diff --git a/its/ruling/pom.xml b/its/ruling/pom.xml index 561f909b9ad..8643c530679 100644 --- a/its/ruling/pom.xml +++ b/its/ruling/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java-its - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT it-java-ruling diff --git a/java-checks-aws/pom.xml b/java-checks-aws/pom.xml index 49eaa44f6e3..66175d83e48 100644 --- a/java-checks-aws/pom.xml +++ b/java-checks-aws/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-checks-aws diff --git a/java-checks-common/pom.xml b/java-checks-common/pom.xml index a9da2efd44b..acd1d59d461 100644 --- a/java-checks-common/pom.xml +++ b/java-checks-common/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-checks-common diff --git a/java-checks-test-sources/aws/pom.xml b/java-checks-test-sources/aws/pom.xml index 2eb99a8476a..e1b8729ee9c 100644 --- a/java-checks-test-sources/aws/pom.xml +++ b/java-checks-test-sources/aws/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT aws diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml index 9bf55e511d8..be24361a796 100644 --- a/java-checks-test-sources/default/pom.xml +++ b/java-checks-test-sources/default/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT default diff --git a/java-checks-test-sources/java-17/pom.xml b/java-checks-test-sources/java-17/pom.xml index 17002d30f4c..f01649f1499 100644 --- a/java-checks-test-sources/java-17/pom.xml +++ b/java-checks-test-sources/java-17/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-17 diff --git a/java-checks-test-sources/pom.xml b/java-checks-test-sources/pom.xml index 9c33b994847..3700b3c2919 100644 --- a/java-checks-test-sources/pom.xml +++ b/java-checks-test-sources/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-checks-test-sources diff --git a/java-checks-test-sources/spring-3.2/pom.xml b/java-checks-test-sources/spring-3.2/pom.xml index e5801819238..2bcffd157ec 100644 --- a/java-checks-test-sources/spring-3.2/pom.xml +++ b/java-checks-test-sources/spring-3.2/pom.xml @@ -7,7 +7,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT spring-3.2 diff --git a/java-checks-test-sources/spring-web-4.0/pom.xml b/java-checks-test-sources/spring-web-4.0/pom.xml index bf9dc7664f9..c8beef683f7 100644 --- a/java-checks-test-sources/spring-web-4.0/pom.xml +++ b/java-checks-test-sources/spring-web-4.0/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT spring-web-4.0 diff --git a/java-checks-test-sources/test-classpath-reader/pom.xml b/java-checks-test-sources/test-classpath-reader/pom.xml index 5f96c1b248b..1c16e09b2dc 100644 --- a/java-checks-test-sources/test-classpath-reader/pom.xml +++ b/java-checks-test-sources/test-classpath-reader/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.java java-checks-test-sources - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT test-classpath-reader diff --git a/java-checks-testkit/pom.xml b/java-checks-testkit/pom.xml index e67013c4028..08826224f0c 100644 --- a/java-checks-testkit/pom.xml +++ b/java-checks-testkit/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-checks-testkit diff --git a/java-checks/pom.xml b/java-checks/pom.xml index 7481c664531..130a5762dc5 100644 --- a/java-checks/pom.xml +++ b/java-checks/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-checks diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml index ee31c6015ce..d630fe97389 100644 --- a/java-frontend/pom.xml +++ b/java-frontend/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-frontend diff --git a/java-jsp/pom.xml b/java-jsp/pom.xml index b7ff4403d9a..3bd5bb7b031 100644 --- a/java-jsp/pom.xml +++ b/java-jsp/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-jsp diff --git a/java-surefire/pom.xml b/java-surefire/pom.xml index 97f0eb82f94..6386b32596a 100644 --- a/java-surefire/pom.xml +++ b/java-surefire/pom.xml @@ -5,7 +5,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT java-surefire diff --git a/pom.xml b/pom.xml index 5f52c0b4a40..5ac45155d71 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT pom SonarJava Code Analyzer for Java :: Parent POM diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml index 278a2c704bb..39531c9e559 100644 --- a/sonar-java-plugin/pom.xml +++ b/sonar-java-plugin/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.java java - 8.34.0-SNAPSHOT + 8.35.0-SNAPSHOT sonar-java-plugin From 0f2433515147dc87716ad84760259615af0f66e1 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 24 Jun 2026 16:04:58 +0200 Subject: [PATCH 21/22] Update autoscan expected test results for S8909 Add expected autoscan diff for new rule S8909 (3 false positives in autoscan mode) and update S6813 false negatives count (65 -> 68). Update test to expect 11 rules causing FPs instead of 10. Co-Authored-By: Claude Sonnet 4.5 --- its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json index a0a3f51e87b..1ffedb0c639 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json @@ -3,4 +3,4 @@ "hasTruePositives": true, "falseNegatives": 69, "falsePositives": 0 -} \ No newline at end of file +} From 4e8989df0050b9a1861df55a15e126eb9f3a40a5 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Wed, 1 Jul 2026 07:34:11 +0200 Subject: [PATCH 22/22] Improve S8909 branch coverage by simplifying unreachable code Remove the unreachable isInterface() guard in isApplicableClass since the visitor only subscribes to Tree.Kind.CLASS. Replace the switch expression in matchesAnnotation with if/else to eliminate the unreachable default branch. Add anonymous class and fully-qualified SessionScoped test samples to cover remaining branches. Co-Authored-By: Claude Opus 4.6 --- ...cheKeyGeneratorInstantiableCheckSample.java | 18 ++++++++++++++++++ .../CacheKeyGeneratorInstantiableCheck.java | 16 +++++++--------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java index a060560e5be..e113baea4f6 100644 --- a/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -155,6 +155,24 @@ abstract class CompliantAbstractClass implements CacheKeyGenerator { interface CompliantInterface extends CacheKeyGenerator { } +class AnonymousClassUser { + // Anonymous class implementing CacheKeyGenerator - covers isAnonymous check (line 72) + CacheKeyGenerator generator = new CacheKeyGenerator() { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } + }; +} + +@jakarta.enterprise.context.SessionScoped +class CompliantFullyQualifiedSessionScoped implements CacheKeyGenerator { + @Override + public Object generate(Method method, Object... methodParams) { + return methodParams[0]; + } +} + class NotACacheKeyGenerator { public NotACacheKeyGenerator(String param) {} } diff --git a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java index a3a518039e0..51743458ea2 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -64,7 +64,6 @@ public void visitNode(Tree tree) { private static boolean isApplicableClass(ClassTree classTree) { return !isAnonymous(classTree) && !classTree.symbol().isAbstract() - && !classTree.symbol().isInterface() && implementsCacheKeyGenerator(classTree); } @@ -85,14 +84,13 @@ private static boolean hasCdiScopeAnnotation(ClassTree classTree) { private static boolean matchesAnnotation(AnnotationTree annotationTree, String fullyQualifiedName) { String simpleName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1); Tree annotationType = annotationTree.annotationType(); - return switch (annotationType.kind()) { - case IDENTIFIER -> ((IdentifierTree) annotationType).name().equals(simpleName); - case MEMBER_SELECT -> { - MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType; - yield memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName); - } - default -> false; - }; + if (annotationType.is(Tree.Kind.IDENTIFIER)) { + return ((IdentifierTree) annotationType).name().equals(simpleName); + } else if (annotationType.is(Tree.Kind.MEMBER_SELECT)) { + MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) annotationType; + return memberSelect.identifier().name().equals(simpleName) || memberSelect.expression().symbolType().is(fullyQualifiedName); + } + return false; } private static boolean hasPublicNoArgsConstructor(ClassTree classTree) {