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..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 @@ -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,51 @@ 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 + @lombok.NonNull + 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); + } + +} + +@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);