From 5411f79bd78b29ab6fc41937609f1ee4af92d7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Mar 2026 15:47:40 +0100 Subject: [PATCH 1/3] Fix FN for S3749 with Lombok annotations --- .../S3749_DefaultAnnotations.java | 47 +++++++++++++++++++ ...ComponentWithNonAutowiredMembersCheck.java | 17 ++++++- .../org/sonar/java/filters/LombokFilter.java | 4 +- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java index c6efb36f66a..1eec6759d54 100644 --- a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java +++ b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java @@ -5,6 +5,8 @@ import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -200,3 +202,48 @@ class JakartaRepo { @jakarta.annotation.Resource String email2 = null; // Compliant } + +@Service +@RequiredArgsConstructor +class LombokInjected { + + private final String injected; // Compliant + private String notInjected; // Noncompliant {{Annotate this member with "@Autowired", "@Resource", "@Inject", or "@Value", or remove it.}} +// ^^^^^^^^^^^ + + public void foo() { + System.out.println(injected); + System.out.println(notInjected); + } + +} + +@Component +@RequiredArgsConstructor +class LombokInjected1 { + + private final String injected; // Compliant + private String notInjected; // Noncompliant {{Annotate this member with "@Autowired", "@Resource", "@Inject", or "@Value", or remove it.}} +// ^^^^^^^^^^^ + + public void foo() { + System.out.println(injected); + System.out.println(notInjected); + } + +} + +@Service +@AllArgsConstructor +class LombokInjected2 { + + private final String injected; // Compliant + private String notInjected; // Compliant + + + public void foo() { + System.out.println(injected); + System.out.println(notInjected); + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentWithNonAutowiredMembersCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentWithNonAutowiredMembersCheck.java index 0ad576102e5..2a13f5c7519 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentWithNonAutowiredMembersCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/SpringComponentWithNonAutowiredMembersCheck.java @@ -70,15 +70,22 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { ClassTree clazzTree = (ClassTree) tree; - Set symbolsUsedInConstructors = symbolsUsedInConstructors(clazzTree); + SymbolMetadata classMetadata = clazzTree.symbol().metadata(); + + if (classMetadata.isAnnotatedWith("lombok.AllArgsConstructor")) { + return; + } + boolean requiredArgsConstructorAnnotationPresent = classMetadata.isAnnotatedWith("lombok.RequiredArgsConstructor"); - if (isSpringSingletonComponent(clazzTree.symbol().metadata())) { + Set symbolsUsedInConstructors = symbolsUsedInConstructors(clazzTree); + if (isSpringSingletonComponent(classMetadata)) { clazzTree.members().stream().filter(v -> v.is(Tree.Kind.VARIABLE)) .map(VariableTree.class::cast) .filter(v -> !v.symbol().isStatic()) .filter(v -> !isSpringInjectionAnnotated(v.symbol().metadata())) .filter(v -> !isCustomInjectionAnnotated(v.symbol().metadata())) .filter(v -> !symbolsUsedInConstructors.contains(v.symbol())) + .filter(v -> !isInjectedByLombok(v, requiredArgsConstructorAnnotationPresent)) .forEach(v -> reportIssue(v.simpleName(), "Annotate this member with \"@Autowired\", \"@Resource\", \"@Inject\", or \"@Value\", or remove it.")); } } @@ -103,6 +110,12 @@ private static boolean isUsingConfigurationProperties(SymbolMetadata classMeta) return classMeta.isAnnotatedWith("org.springframework.boot.context.properties.ConfigurationProperties"); } + private static boolean isInjectedByLombok(VariableTree field, boolean requiredArgsConstructorAnnotationPresent) { + return requiredArgsConstructorAnnotationPresent + && field.initializer() == null + && (field.symbol().isFinal() || field.symbol().metadata().isAnnotatedWith("lombok.NonNull")); + } + private Set symbolsUsedInConstructors(ClassTree clazzTree) { List constructors = constructors(clazzTree); return constructors.stream() diff --git a/java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java b/java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java index 388d0bc49ab..54bfd30af29 100644 --- a/java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java +++ b/java-checks/src/main/java/org/sonar/java/filters/LombokFilter.java @@ -37,7 +37,6 @@ import org.sonar.java.checks.UtilityClassWithPublicConstructorCheck; import org.sonar.java.checks.helpers.ExpressionsHelper; import org.sonar.java.checks.naming.BadFieldNameCheck; -import org.sonar.java.checks.spring.SpringComponentWithNonAutowiredMembersCheck; import org.sonar.java.checks.tests.AssertionTypesCheck; import org.sonar.java.checks.unused.UnusedPrivateFieldCheck; import org.sonar.plugins.java.api.JavaCheck; @@ -69,7 +68,6 @@ public class LombokFilter extends BaseTreeVisitorIssueFilter { /* S1450 */ PrivateFieldUsedLocallyCheck.class, /* S4248 */ RegexPatternsNeedlesslyCheck.class, /* S2159 */ SillyEqualsCheck.class, - /* S3749 */ SpringComponentWithNonAutowiredMembersCheck.class, /* S2325 */ StaticMethodCheck.class, /* S1068 */ UnusedPrivateFieldCheck.class, /* S1128 */ UselessImportCheck.class, @@ -130,7 +128,7 @@ public void visitClass(ClassTree tree) { boolean generatesEquals = usesAnnotation(tree, GENERATE_EQUALS); excludeLinesIfTrue(generatesEquals || usesAnnotation(tree, GENERATE_UNUSED_FIELD_RELATED_METHODS), tree, UnusedPrivateFieldCheck.class, PrivateFieldUsedLocallyCheck.class); - excludeLinesIfTrue(usesAnnotation(tree, GENERATE_CONSTRUCTOR), tree, AtLeastOneConstructorCheck.class, SpringComponentWithNonAutowiredMembersCheck.class); + excludeLinesIfTrue(usesAnnotation(tree, GENERATE_CONSTRUCTOR), tree, AtLeastOneConstructorCheck.class); excludeLinesIfTrue(generatesEquals, tree, EqualsNotOverriddenInSubclassCheck.class, EqualsNotOverriddenWithCompareToCheck.class); excludeLinesIfTrue(generatesNonPublicConstructor(tree), tree, UtilityClassWithPublicConstructorCheck.class); boolean isUtilityClass = usesAnnotation(tree, UTILITY_CLASS); From 974442dd041ca2deb85efa1920822d3a67098b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Mar 2026 16:52:44 +0100 Subject: [PATCH 2/3] Add another example annotated with NonNull to the test samples Co-authored-by: lpilastri <115481625+leonardo-pilastri-sonarsource@users.noreply.github.com> --- .../S3749_DefaultAnnotations.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java index 1eec6759d54..16a212b4be7 100644 --- a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java +++ b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java @@ -223,6 +223,8 @@ public void foo() { class LombokInjected1 { private final String injected; // Compliant + @lombok.NonNull + private String notInjectedButNonNull; // Compliant private String notInjected; // Noncompliant {{Annotate this member with "@Autowired", "@Resource", "@Inject", or "@Value", or remove it.}} // ^^^^^^^^^^^ From f67480a869317949b5674a9aadd46963dd171f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Thu, 5 Mar 2026 16:56:29 +0100 Subject: [PATCH 3/3] Use the additional field to pass the autoscan tests --- .../S3749_DefaultAnnotations.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java index 16a212b4be7..e41af8f559e 100644 --- a/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java +++ b/java-checks-test-sources/default/src/main/java/checks/S3749_SpringComponentWithNonAutowiredMembersCheck/S3749_DefaultAnnotations.java @@ -224,12 +224,13 @@ class LombokInjected1 { private final String injected; // Compliant @lombok.NonNull - private String notInjectedButNonNull; // Compliant + private String injectedNonNull; // Compliant private String notInjected; // Noncompliant {{Annotate this member with "@Autowired", "@Resource", "@Inject", or "@Value", or remove it.}} // ^^^^^^^^^^^ public void foo() { System.out.println(injected); + System.out.println(injectedNonNull); System.out.println(notInjected); }