Skip to content

Commit b9ee2c1

Browse files
SONARJAVA-6184 S4605: FP when having SpringBootApplication followed by ComponentScan annotation (#5510)
1 parent 34bfb95 commit b9ee2c1

4 files changed

Lines changed: 63 additions & 14 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package checks.spring.s4605.mixed.app1;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.context.annotation.ComponentScan;
6+
import org.springframework.context.annotation.FilterType;
7+
8+
@SpringBootApplication
9+
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.REGEX, pattern = "checks.spring.s4605.mixed.app1.smth")})
10+
public class App1 {
11+
static void main(String[] args) {
12+
SpringApplication.run(checks.spring.s4605.mixed.app1.App1.class, args);
13+
}
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package checks.spring.s4605.mixed.app1.visible;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
interface VisibleServiceI {
6+
}
7+
8+
@Component
9+
public class VisibleService implements VisibleServiceI { // Compliant
10+
}

java-checks/src/main/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheck.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class SpringBeansShouldBeAccessibleCheck extends IssuableSubscriptionVisi
6565
};
6666

6767
private static final String COMPONENT_SCAN_ANNOTATION = "org.springframework.context.annotation.ComponentScan";
68-
private static final Set<String> COMPONENT_SCAN_ARGUMENTS = SetUtils.immutableSetOf("basePackages", "basePackageClasses", "value");
68+
private static final Set<String> COMPONENT_SCAN_BASE_ARGUMENTS = SetUtils.immutableSetOf("basePackages", "basePackageClasses", "value");
6969

7070
private static final String CACHE_KEY_PREFIX = "java:S4605:targeted:";
7171

@@ -117,16 +117,17 @@ public void visitNode(Tree tree) {
117117
String classPackageName = packageNameOf(classTree.symbol());
118118
SymbolMetadata classSymbolMetadata = classTree.symbol().metadata();
119119

120-
121-
List<SymbolMetadata.AnnotationValue> componentScanValues = classSymbolMetadata.valuesForAnnotation(COMPONENT_SCAN_ANNOTATION);
122-
if (componentScanValues != null) {
123-
componentScanValues.forEach(this::addToScannedPackages);
124-
} else if (hasAnnotation(classSymbolMetadata, SpringUtils.SPRING_BOOT_APP_ANNOTATION)) {
125-
var targetedPackages = targetedPackages(classPackageName, classSymbolMetadata);
126-
packagesScannedBySpringAtProjectLevel.addAll(targetedPackages);
127-
packagesScannedBySpringAtFileLevel.addAll(targetedPackages);
128-
} else if (hasAnnotation(classSymbolMetadata, SPRING_BEAN_ANNOTATIONS)) {
129-
addMessageToMap(classPackageName, classTree.simpleName());
120+
// try to apply "direct" annotation first
121+
if (!handledByComponentScan(classSymbolMetadata)) {
122+
if (hasAnnotation(classSymbolMetadata, SpringUtils.SPRING_BOOT_APP_ANNOTATION)) {
123+
// apply scan setting from @SpringBootApplication annotation
124+
var targetedPackages = targetedPackages(classPackageName, classSymbolMetadata);
125+
packagesScannedBySpringAtProjectLevel.addAll(targetedPackages);
126+
packagesScannedBySpringAtFileLevel.addAll(targetedPackages);
127+
} else if (hasAnnotation(classSymbolMetadata, SPRING_BEAN_ANNOTATIONS)) {
128+
// include this class as a candidate for issue reporting
129+
addMessageToMap(classPackageName, classTree.simpleName());
130+
}
130131
}
131132
}
132133

@@ -145,6 +146,20 @@ public void leaveFile(JavaFileScannerContext context) {
145146
packagesScannedBySpringAtFileLevel.clear();
146147
}
147148

149+
private boolean handledByComponentScan(SymbolMetadata classSymbolMetadata) {
150+
boolean handledByComponentScan = false;
151+
List<SymbolMetadata.AnnotationValue> componentScanAttributes = classSymbolMetadata.valuesForAnnotation(COMPONENT_SCAN_ANNOTATION);
152+
if (componentScanAttributes != null) {
153+
List<SymbolMetadata.AnnotationValue> componentScanBaseAttributes = componentScanAttributes.stream().filter(v -> COMPONENT_SCAN_BASE_ARGUMENTS.contains(v.name())).toList();
154+
if (!componentScanBaseAttributes.isEmpty()) {
155+
handledByComponentScan = true;
156+
componentScanBaseAttributes.forEach(this::addToScannedPackages);
157+
}
158+
}
159+
160+
return handledByComponentScan;
161+
}
162+
148163
private static String cacheKey(InputFile inputFile) {
149164
return CACHE_KEY_PREFIX + inputFile.key();
150165
}
@@ -200,9 +215,6 @@ private void addMessageToMap(String classPackageName, IdentifierTree classNameTr
200215
}
201216

202217
private void addToScannedPackages(SymbolMetadata.AnnotationValue annotationValue) {
203-
if (!COMPONENT_SCAN_ARGUMENTS.contains(annotationValue.name())) {
204-
return;
205-
}
206218
if (annotationValue.value() instanceof Object[] objects) {
207219
for (Object o : objects) {
208220
if (o instanceof String oString) {

java-checks/src/test/java/org/sonar/java/checks/spring/SpringBeansShouldBeAccessibleCheckTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,19 @@ void testSpringBootApplicationWithAnnotation() {
157157
.verifyIssues();
158158
}
159159

160+
@Test
161+
void testBothAnnotationsTogether() {
162+
final String folderApp = BASE_PATH + "mixed/app1/";
163+
List<String> testFiles = Arrays.asList(
164+
mainCodeSourcesPath(folderApp + "App1.java"),
165+
mainCodeSourcesPath(folderApp + "visible/VisibleService.java"));
166+
167+
CheckVerifier.newVerifier()
168+
.onFiles(testFiles)
169+
.withCheck(new SpringBeansShouldBeAccessibleCheck())
170+
.verifyNoIssues();
171+
}
172+
160173
@Test
161174
void caching() throws NoSuchAlgorithmException, IOException {
162175
var unchangedFiles = Stream.of(

0 commit comments

Comments
 (0)