diff --git a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json index cd487842983..26081c34a82 100644 --- a/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json +++ b/its/autoscan/src/test/resources/autoscan/autoscan-diff-by-rules.json @@ -2938,5 +2938,11 @@ "hasTruePositives": false, "falseNegatives": 2, "falsePositives": 0 + }, + { + "ruleKey": "S6862", + "hasTruePositives": false, + "falseNegatives": 5, + "falsePositives": 0 } ] diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6862.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6862.json new file mode 100644 index 00000000000..7c7d34ddc29 --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6862.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S6862", + "hasTruePositives": false, + "falseNegatives": 5, + "falsePositives": 0 +} diff --git a/java-checks-test-sources/default/src/main/java/checks/ConfigurationBeanNamesCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ConfigurationBeanNamesCheckSample.java new file mode 100644 index 00000000000..4fe87ba6aa6 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ConfigurationBeanNamesCheckSample.java @@ -0,0 +1,110 @@ +package checks; + +import javax.annotation.Nullable; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +public class ConfigurationBeanNamesCheckSample { + + class User { + } + + @Configuration + class Config1 { + @Bean + public User user() { + return new User(); + } + + @Bean + public User user(String name) { // Noncompliant [[sc=17;ec=21]] {{Rename this bean method to prevent any conflict with other beans.}} + return new User(); + } + } + + @Configuration + class Config2 { + @Bean + public User user() { + return new User(); + } + + @Bean + public User userWithName(String name) { // Compliant + return new User(); + } + } + + @Configuration + class Config3 { + @Bean + public User user() { // Compliant + return new User(); + } + } + + @Configuration + class Config4 { + @Bean + public User user() { // Compliant + return new User(); + } + + @Nullable + public User user(String name) { + return new User(); + } + } + + @Configuration + class Config5 { + @Bean + public User user() { + return new User(); + } + + @Bean + public User userWithName(String name) { // Compliant + return new User(); + } + + @Bean + public User user(String name, String password) { // Noncompliant + return new User(); + } + + @Bean + public User user(String name, String password, boolean enabled) { // Noncompliant + return new User(); + } + } + + @Configuration + class Config6 { + } + + @Configuration + class Config7 { + @Bean + public User user1() { + return new User(); + } + + @Bean + public User user1(String name) { // Noncompliant + return new User(); + } + + @Bean + public User user2() { + return new User(); + } + + @Bean + public User user2(String name) { // Noncompliant + return new User(); + } + + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ConfigurationBeanNamesCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ConfigurationBeanNamesCheck.java new file mode 100644 index 00000000000..3ed366e3cac --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/ConfigurationBeanNamesCheck.java @@ -0,0 +1,67 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.java.checks; + +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S6862") +public class ConfigurationBeanNamesCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + var classTree = (ClassTree) tree; + if (!isConfigurationClass(classTree)) { + return; + } + + var beanMethods = getBeanMethods(classTree); + var foundNames = new HashSet(); + for (MethodTree beanMethod : beanMethods) { + if (!foundNames.add(beanMethod.simpleName().name())) { + reportIssue(beanMethod.simpleName(), "Rename this bean method to prevent any conflict with other beans."); + } + } + } + + private static boolean isConfigurationClass(ClassTree classTree) { + return classTree.symbol().metadata().isAnnotatedWith("org.springframework.context.annotation.Configuration"); + } + + private static List getBeanMethods(ClassTree classTree) { + return classTree.members().stream() + .filter(member -> member.is(Tree.Kind.METHOD)) + .map(MethodTree.class::cast) + .filter(method -> method.symbol().metadata().isAnnotatedWith("org.springframework.context.annotation.Bean")) + .collect(Collectors.toList()); + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ConfigurationBeanNamesCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ConfigurationBeanNamesCheckTest.java new file mode 100644 index 00000000000..4d58c5f11a3 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/ConfigurationBeanNamesCheckTest.java @@ -0,0 +1,36 @@ +/* + * SonarQube Java + * Copyright (C) 2012-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; +import org.sonar.java.checks.verifier.TestUtils; + +class ConfigurationBeanNamesCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(TestUtils.mainCodeSourcesPath("checks/ConfigurationBeanNamesCheckSample.java")) + .withCheck(new ConfigurationBeanNamesCheck()) + .verifyIssues(); + } + +} diff --git a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java index 22400ce1816..21e632ab86d 100644 --- a/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java +++ b/sonar-java-plugin/src/main/java/org/sonar/plugins/java/CheckList.java @@ -96,6 +96,7 @@ import org.sonar.java.checks.CompareToReturnValueCheck; import org.sonar.java.checks.ConcatenationWithStringValueOfCheck; import org.sonar.java.checks.ConditionalOnNewLineCheck; +import org.sonar.java.checks.ConfigurationBeanNamesCheck; import org.sonar.java.checks.ConfusingOverloadCheck; import org.sonar.java.checks.ConfusingVarargCheck; import org.sonar.java.checks.ConstantMathCheck; @@ -738,6 +739,7 @@ public final class CheckList { BooleanMethodNameCheck.class, BooleanMethodReturnCheck.class, BrainMethodCheck.class, + ConfigurationBeanNamesCheck.class, CORSCheck.class, CallOuterPrivateMethodCheck.class, CallSuperMethodFromInnerClassCheck.class, diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.html new file mode 100644 index 00000000000..311b70bcffc --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.html @@ -0,0 +1,47 @@ +

Why is this an issue?

+

Naming conventions play a crucial role in maintaining code clarity and readability. The uniqueness of bean names in Spring configurations is vital +to the clarity and readability of the code. When two beans share the same name within a configuration, it is not obvious to the reader which bean is +being referred to. This leads to potential misunderstandings and errors.

+

How to fix it

+

To address this issue, ensure each bean within a configuration has a distinct and meaningful name. Choose names that accurately represent the +purpose or functionality of the bean.

+

Code examples

+

Noncompliant code example

+
+@Configuration
+class Config {
+  @Bean
+  public User user() {
+    return currentUser();
+  }
+  @Bean
+  public User user(AuthService auth) { // Noncompliant
+    return auth.user();
+  }
+}
+
+

Compliant solution

+
+@Configuration
+class Config {
+  @Bean
+  public User user() {
+    return currentUser();
+  }
+  @Bean
+  public User userFromAuth(AuthService auth) {
+    return auth.user();
+  }
+}
+
+

Resources

+

Documentation

+ + diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json new file mode 100644 index 00000000000..a6e9129323f --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S6862.json @@ -0,0 +1,23 @@ +{ + "title": "Beans in \"@Configuration\" class should have different names", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "spring" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-6862", + "sqKey": "S6862", + "scope": "Main", + "quickfix": "unknown", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "CLEAR" + } +} 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 2693955d808..77ec0dd56a4 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 @@ -495,6 +495,7 @@ "S6832", "S6833", "S6837", - "S6856" + "S6856", + "S6862" ] }