From 700dbe2f49121f38904e8062da3f15707be804cf Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 13 Jul 2026 12:22:50 +0200 Subject: [PATCH 1/7] Implement new rule S5673 This rule detects classes annotated with @Component whose names suggest they should use specialized Spring stereotype annotations (@Service, @Repository, @Controller, or @RestController) instead. --- ...ingComponentSpecializationCheckSample.java | 125 ++++++++++++++++++ .../SpringComponentSpecializationCheck.java | 93 +++++++++++++ ...pringComponentSpecializationCheckTest.java | 33 +++++ .../org/sonar/l10n/java/rules/java/S5673.html | 45 +++++++ .../org/sonar/l10n/java/rules/java/S5673.json | 23 ++++ .../main/resources/profiles/Sonar_way/S5673 | 0 6 files changed, 319 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.json create mode 100644 sonar-java-plugin/src/main/resources/profiles/Sonar_way/S5673 diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java new file mode 100644 index 00000000000..a041578deb0 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java @@ -0,0 +1,125 @@ +package checks.spring; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RestController; + +public class SpringComponentSpecializationCheckSample { + + // Service patterns + + @Component // Noncompliant {{Use @Service instead of @Component}} + public class CustomerServiceImpl { + } + + @Component // Noncompliant {{Use @Service instead of @Component}} + public class OrderService { + } + + @Component // Noncompliant {{Use @Service instead of @Component}} + public class PaymentServiceFacade { + } + + // Repository patterns + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public class ProductRepository { + } + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public class UserRepositoryImpl { + } + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public class OrderDao { + } + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public class CustomerDao { + } + + // RestController patterns + + @Component // Noncompliant {{Use @RestController instead of @Component}} + public class FooBarRestController { + } + + @Component // Noncompliant {{Use @RestController instead of @Component}} + public class ApiRestController { + } + + @Component // Noncompliant {{Use @RestController instead of @Component}} + public class UserRestControllerImpl { + } + + // Controller patterns + + @Component // Noncompliant {{Use @Controller instead of @Component}} + public class HomeController { + } + + @Component // Noncompliant {{Use @Controller instead of @Component}} + public class LoginControllerImpl { + } + + // Compliant - Correct annotations used + + @Service + public class CustomerServiceImplCorrect { + } + + @Repository + public class ProductRepositoryCorrect { + } + + @RestController + public class FooBarRestControllerCorrect { + } + + @Controller + public class HomeControllerCorrect { + } + + // Generic component names + + @Component + public class SomeOtherComponent { + } + + @Component + public class UtilityHelper { + } + + @Component + public class CacheManager { + } + + @Component + public class DataProcessor { + } + + // No annotation + + public class PlainClass { + } + + // Case variations + + @Component // Noncompliant {{Use @Service instead of @Component}} + public class userservice { + } + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public class USERREPOSITORY { + } + + @Component // Noncompliant {{Use @Controller instead of @Component}} + public class maincontroller { + } + + @Component // Noncompliant {{Use @RestController instead of @Component}} + public class apirestcontroller { + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java new file mode 100644 index 00000000000..e6196eba359 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java @@ -0,0 +1,93 @@ +/* + * 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.spring; + +import java.util.List; +import java.util.Optional; +import javax.annotation.CheckForNull; +import org.sonar.check.Rule; +import org.sonar.java.checks.helpers.SpringUtils; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.AnnotationTree; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S5673") +public class SpringComponentSpecializationCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return List.of(Tree.Kind.CLASS); + } + + @Override + public void visitNode(Tree tree) { + var classTree = (ClassTree) tree; + + Optional componentAnnotation = classTree.modifiers().annotations().stream() + .filter(a -> SpringUtils.COMPONENT_ANNOTATION.equals(a.annotationType().symbolType().fullyQualifiedName())) + .findFirst(); + + if (componentAnnotation.isEmpty()) { + return; + } + + String className = classTree.simpleName().name(); + String suggestedAnnotation = getSuggestedAnnotation(className); + + if (suggestedAnnotation != null) { + reportIssue(componentAnnotation.get(), String.format("Use @%s instead of @Component", suggestedAnnotation)); + } + } + + @CheckForNull + private static String getSuggestedAnnotation(String className) { + // Check RestController first to avoid false matches with Controller + if (endsWithIgnoreCase(className, "RestController") || endsWithIgnoreCase(className, "RestControllerImpl")) { + return "RestController"; + } + + if (endsWithIgnoreCase(className, "Controller") || endsWithIgnoreCase(className, "ControllerImpl")) { + return "Controller"; + } + + if (endsWithIgnoreCase(className, "Service") || + endsWithIgnoreCase(className, "ServiceImpl") || + containsIgnoreCase(className, "ServiceFacade")) { + return "Service"; + } + + if (endsWithIgnoreCase(className, "Repository") || + endsWithIgnoreCase(className, "RepositoryImpl") || + endsWithIgnoreCase(className, "Dao")) { + return "Repository"; + } + + return null; + } + + private static boolean endsWithIgnoreCase(String str, String suffix) { + if (str.length() < suffix.length()) { + return false; + } + return str.substring(str.length() - suffix.length()).equalsIgnoreCase(suffix); + } + + private static boolean containsIgnoreCase(String str, String substring) { + return str.toLowerCase().contains(substring.toLowerCase()); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheckTest.java new file mode 100644 index 00000000000..e6998fde07e --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheckTest.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.spring; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class SpringComponentSpecializationCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/spring/SpringComponentSpecializationCheckSample.java")) + .withCheck(new SpringComponentSpecializationCheck()) + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.html new file mode 100644 index 00000000000..74c51da93b2 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.html @@ -0,0 +1,45 @@ +

The Spring Framework provides several specializations of the generic @Component stereotype annotation which better express the programmer's intent. Using them should be preferred.

+

Why is this an issue?

+

Spring provides specialized stereotype annotations (@Service, @Repository, @Controller, and @RestController) that make code more expressive and provide additional semantics. When a class name suggests it should use one of these specialized annotations but uses @Component instead, it reduces code clarity.

+

How to fix it

+

Replace @Component with the appropriate specialized annotation based on the class's role in the application.

+

Code examples

+

Noncompliant code example

+
+@Component // Noncompliant
+public class CustomerServiceImpl {
+  // ...
+}
+
+@Component // Noncompliant
+public class ProductRepository {
+    // ...
+}
+
+@Component // Noncompliant
+public class FooBarRestController {
+    // ...
+}
+
+

Compliant solution

+
+@Service // Compliant
+public class CustomerServiceImpl {
+  // ...
+}
+
+@Repository // Compliant
+public class ProductRepository {
+    // ...
+}
+
+@RestController // Compliant
+public class FooBarRestController {
+    // ...
+}
+
+

Resources

+

Documentation

+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.json new file mode 100644 index 00000000000..66e17c3344f --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5673.json @@ -0,0 +1,23 @@ +{ + "title": "Spring components should use specialized annotations", + "type": "CODE_SMELL", + "code": { + "impacts": { + "MAINTAINABILITY": "LOW" + }, + "attribute": "CLEAR" + }, + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "2min" + }, + "tags": [ + "spring" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-5673", + "sqKey": "S5673", + "scope": "All", + "quickfix": "unknown" +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S5673 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S5673 new file mode 100644 index 00000000000..e69de29bb2d From df023dbd56213bb6abed8ab666b362aa2096b725 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 13 Jul 2026 15:01:55 +0200 Subject: [PATCH 2/7] Fix S1449: use Locale.ROOT in toLowerCase() calls Co-Authored-By: Claude Sonnet 4.6 --- .../java/checks/spring/SpringComponentSpecializationCheck.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java index e6196eba359..8dd7b8c39c0 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java @@ -17,6 +17,7 @@ package org.sonar.java.checks.spring; import java.util.List; +import java.util.Locale; import java.util.Optional; import javax.annotation.CheckForNull; import org.sonar.check.Rule; @@ -88,6 +89,6 @@ private static boolean endsWithIgnoreCase(String str, String suffix) { } private static boolean containsIgnoreCase(String str, String substring) { - return str.toLowerCase().contains(substring.toLowerCase()); + return str.toLowerCase(Locale.ROOT).contains(substring.toLowerCase(Locale.ROOT)); } } From 76ef640ecdbd9c33e02fbd27c765debf71ff983c Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 13 Jul 2026 15:05:44 +0200 Subject: [PATCH 3/7] Fix CI failures - Fixed S1449 violations by adding Locale.ROOT to two toLowerCase() calls in SpringComponentSpecializationCheck.containsIgnoreCase() - Added missing autoscan diff files for new rule S5673 (hasTruePositives=false, falseNegatives=17, falsePositives=0) by creating diff_S5673.json and adding entry to autoscan-diff-by-rules.json - Fixed 2 S1449 code smells in SpringComponentSpecializationCheck.java: added Locale.ROOT to toLowerCase() calls in containsIgnoreCase method (already applied in commit df023dbd56) --- .../src/test/resources/autoscan/autoscan-diff-by-rules.json | 6 ++++++ .../src/test/resources/autoscan/diffs/diff_S5673.json | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json 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 3677d0acf84..55cea45ed01 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 @@ -2117,6 +2117,12 @@ "falseNegatives": 1, "falsePositives": 0 }, + { + "ruleKey": "S5673", + "hasTruePositives": false, + "falseNegatives": 17, + "falsePositives": 0 + }, { "ruleKey": "S5679", "hasTruePositives": false, diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json new file mode 100644 index 00000000000..07775157976 --- /dev/null +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json @@ -0,0 +1,6 @@ +{ + "ruleKey": "S5673", + "hasTruePositives": false, + "falseNegatives": 17, + "falsePositives": 0 +} \ No newline at end of file From 3cf87e0f8ce5e0cb0a09f00c98ba90807b07bdef Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Mon, 13 Jul 2026 16:08:48 +0200 Subject: [PATCH 4/7] S5673: use endsWithIgnoreCase for ServiceFacade match Replace containsIgnoreCase with endsWithIgnoreCase for the ServiceFacade suffix check, consistent with all other name-matching patterns in the rule. This avoids false positives on classes like ServiceFacadeBuilder or ServiceFacadeConfiguration. Remove the now-unused containsIgnoreCase helper. --- .../checks/spring/SpringComponentSpecializationCheck.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java index 8dd7b8c39c0..4e5aa1ba047 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java @@ -17,7 +17,6 @@ package org.sonar.java.checks.spring; import java.util.List; -import java.util.Locale; import java.util.Optional; import javax.annotation.CheckForNull; import org.sonar.check.Rule; @@ -68,7 +67,7 @@ private static String getSuggestedAnnotation(String className) { if (endsWithIgnoreCase(className, "Service") || endsWithIgnoreCase(className, "ServiceImpl") || - containsIgnoreCase(className, "ServiceFacade")) { + endsWithIgnoreCase(className, "ServiceFacade")) { return "Service"; } @@ -88,7 +87,4 @@ private static boolean endsWithIgnoreCase(String str, String suffix) { return str.substring(str.length() - suffix.length()).equalsIgnoreCase(suffix); } - private static boolean containsIgnoreCase(String str, String substring) { - return str.toLowerCase(Locale.ROOT).contains(substring.toLowerCase(Locale.ROOT)); - } } From 1d30c809496cdbf7531648dbf7553cf1889a3ad8 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 14:07:38 +0200 Subject: [PATCH 5/7] S5673: also apply to interfaces Extend the rule to cover interface declarations in addition to classes. This is relevant for Spring Data repository interfaces annotated with @Component, where @Repository would be the appropriate stereotype. --- ...ingComponentSpecializationCheckSample.java | 30 +++++++++++++++++++ .../SpringComponentSpecializationCheck.java | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java index a041578deb0..c31476c6796 100644 --- a/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java @@ -122,4 +122,34 @@ public class maincontroller { @Component // Noncompliant {{Use @RestController instead of @Component}} public class apirestcontroller { } + + // Interface patterns + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public interface UserRepository { + } + + @Component // Noncompliant {{Use @Service instead of @Component}} + public interface PaymentService { + } + + @Component // Noncompliant {{Use @Repository instead of @Component}} + public interface ProductDao { + } + + // Compliant interfaces - correct annotations used + + @Repository + public interface CategoryRepository { + } + + @Service + public interface NotificationService { + } + + // Compliant interfaces - generic names + + @Component + public interface EventListener { + } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java index 4e5aa1ba047..f14ccf9a535 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java @@ -31,7 +31,7 @@ public class SpringComponentSpecializationCheck extends IssuableSubscriptionVisi @Override public List nodesToVisit() { - return List.of(Tree.Kind.CLASS); + return List.of(Tree.Kind.CLASS, Tree.Kind.INTERFACE); } @Override From ffd168d6086af2c54a8b60488c09b99949ecc8d5 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 14:22:09 +0200 Subject: [PATCH 6/7] S5673: suggest renaming as alternative fix in issue message --- ...ingComponentSpecializationCheckSample.java | 38 +++++++++---------- .../SpringComponentSpecializationCheck.java | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java index c31476c6796..8672679f351 100644 --- a/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/spring/SpringComponentSpecializationCheckSample.java @@ -10,57 +10,57 @@ public class SpringComponentSpecializationCheckSample { // Service patterns - @Component // Noncompliant {{Use @Service instead of @Component}} + @Component // Noncompliant {{Use @Service instead of @Component, or rename this type if the @Component annotation is intentional}} public class CustomerServiceImpl { } - @Component // Noncompliant {{Use @Service instead of @Component}} + @Component // Noncompliant {{Use @Service instead of @Component, or rename this type if the @Component annotation is intentional}} public class OrderService { } - @Component // Noncompliant {{Use @Service instead of @Component}} + @Component // Noncompliant {{Use @Service instead of @Component, or rename this type if the @Component annotation is intentional}} public class PaymentServiceFacade { } // Repository patterns - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public class ProductRepository { } - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public class UserRepositoryImpl { } - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public class OrderDao { } - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public class CustomerDao { } // RestController patterns - @Component // Noncompliant {{Use @RestController instead of @Component}} + @Component // Noncompliant {{Use @RestController instead of @Component, or rename this type if the @Component annotation is intentional}} public class FooBarRestController { } - @Component // Noncompliant {{Use @RestController instead of @Component}} + @Component // Noncompliant {{Use @RestController instead of @Component, or rename this type if the @Component annotation is intentional}} public class ApiRestController { } - @Component // Noncompliant {{Use @RestController instead of @Component}} + @Component // Noncompliant {{Use @RestController instead of @Component, or rename this type if the @Component annotation is intentional}} public class UserRestControllerImpl { } // Controller patterns - @Component // Noncompliant {{Use @Controller instead of @Component}} + @Component // Noncompliant {{Use @Controller instead of @Component, or rename this type if the @Component annotation is intentional}} public class HomeController { } - @Component // Noncompliant {{Use @Controller instead of @Component}} + @Component // Noncompliant {{Use @Controller instead of @Component, or rename this type if the @Component annotation is intentional}} public class LoginControllerImpl { } @@ -107,33 +107,33 @@ public class PlainClass { // Case variations - @Component // Noncompliant {{Use @Service instead of @Component}} + @Component // Noncompliant {{Use @Service instead of @Component, or rename this type if the @Component annotation is intentional}} public class userservice { } - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public class USERREPOSITORY { } - @Component // Noncompliant {{Use @Controller instead of @Component}} + @Component // Noncompliant {{Use @Controller instead of @Component, or rename this type if the @Component annotation is intentional}} public class maincontroller { } - @Component // Noncompliant {{Use @RestController instead of @Component}} + @Component // Noncompliant {{Use @RestController instead of @Component, or rename this type if the @Component annotation is intentional}} public class apirestcontroller { } // Interface patterns - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public interface UserRepository { } - @Component // Noncompliant {{Use @Service instead of @Component}} + @Component // Noncompliant {{Use @Service instead of @Component, or rename this type if the @Component annotation is intentional}} public interface PaymentService { } - @Component // Noncompliant {{Use @Repository instead of @Component}} + @Component // Noncompliant {{Use @Repository instead of @Component, or rename this type if the @Component annotation is intentional}} public interface ProductDao { } diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java index f14ccf9a535..a91e398f42b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentSpecializationCheck.java @@ -50,7 +50,7 @@ public void visitNode(Tree tree) { String suggestedAnnotation = getSuggestedAnnotation(className); if (suggestedAnnotation != null) { - reportIssue(componentAnnotation.get(), String.format("Use @%s instead of @Component", suggestedAnnotation)); + reportIssue(componentAnnotation.get(), String.format("Use @%s instead of @Component, or rename this type if the @Component annotation is intentional", suggestedAnnotation)); } } From f1025618de97416f7651a3a774e9dd4fc3ed7b6a Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 15:02:26 +0200 Subject: [PATCH 7/7] Update autoscan results --- its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json index 07775157976..559fd7787c9 100644 --- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json +++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5673.json @@ -1,6 +1,6 @@ { "ruleKey": "S5673", "hasTruePositives": false, - "falseNegatives": 17, + "falseNegatives": 20, "falsePositives": 0 } \ No newline at end of file