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 }} 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/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6813.json index a9b3acaf13a..1ffedb0c639 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 +} diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json similarity index 77% rename from its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json rename to its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json index 4cb9dce7c70..f5fab362944 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6548.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S8909.json @@ -1,5 +1,5 @@ { - "ruleKey": "S6548", + "ruleKey": "S8909", "hasTruePositives": true, "falseNegatives": 0, "falsePositives": 0 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/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..e113baea4f6 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java @@ -0,0 +1,193 @@ +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 NoncompliantPackagePrivateImplicitConstructor implements CacheKeyGenerator { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + @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]; + } +} + +@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 { +} + +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) {} +} + +class ConfigService { + public String getPrefix() { + return "prefix"; + } +} + +class DatabaseService { + public String format(Object obj) { + return obj.toString(); + } +} + +class CacheConfig { +} 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-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-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..51743458ea2 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheck.java @@ -0,0 +1,109 @@ +/* + * 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.Collection; +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.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") +public class CacheKeyGeneratorInstantiableCheck extends IssuableSubscriptionVisitor { + + private static final String CACHE_KEY_GENERATOR = "io.quarkus.cache.CacheKeyGenerator"; + + private static final List CDI_SCOPE_ANNOTATIONS = List.of( + "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 List.of(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + ClassTree classTree = (ClassTree) tree; + if (!isApplicableClass(classTree) || 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) { + return !isAnonymous(classTree) + && !classTree.symbol().isAbstract() + && 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) + || 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(); + 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) { + 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(); + } +} 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..a4f0dba2f57 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/quarkus/CacheKeyGeneratorInstantiableCheckTest.java @@ -0,0 +1,42 @@ +/* + * 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(); + } + + @Test + void testWithoutSemantic() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/quarkus/CacheKeyGeneratorInstantiableCheckSample.java")) + .withCheck(new CacheKeyGeneratorInstantiableCheck()) + .withoutSemantic() + .verifyNoIssues(); + } +} 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-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 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 f7229dba8f0..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 @@ -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 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/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/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/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 73bf9f71841..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 @@ -402,7 +402,6 @@ "S6485", "S6539", "S6541", - "S6548", "S6806", "S6809", "S6810", @@ -464,7 +463,6 @@ "S8465", "S8469", "S8696", - "S8714", "S8715", "S8745", "S8786" 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 7fb0e749028..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", @@ -534,6 +533,7 @@ "S8715", "S8745", "S8786", + "S8909", "S8911", "S8924" ] 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..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(467) + .hasSize(465) .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", @@ -122,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