[lombok] IDEA-375529 False positive warning by "Lombok annotations" inspection regarding @Data

GitOrigin-RevId: 68e68e3ac175b44d8b5b066c138e97c721936b71
This commit is contained in:
Michail Plushnikov
2025-07-06 17:29:17 +00:00
committed by intellij-monorepo-bot
parent 83d4ecc4a6
commit 012fe6bfde
13 changed files with 131 additions and 101 deletions
@@ -60,8 +60,8 @@ public abstract class AbstractProcessor implements Processor {
return true;
}
protected @Unmodifiable @NotNull <T extends PsiModifierListOwner> Collection<T> filterToleratedElements(@NotNull @Unmodifiable Collection<? extends T> definedMethods) {
return ContainerUtil.filter(definedMethods, definedMethod -> !PsiAnnotationSearchUtil.isAnnotatedWith(definedMethod, LombokClassNames.TOLERATE));
protected static @Unmodifiable @NotNull <T extends PsiModifierListOwner> Collection<T> filterToleratedElements(@NotNull @Unmodifiable Collection<? extends T> definedMethods) {
return ContainerUtil.filter(definedMethods, definedMethod -> PsiAnnotationSearchUtil.isNotAnnotatedWith(definedMethod, LombokClassNames.TOLERATE));
}
protected boolean readAnnotationOrConfigProperty(@NotNull PsiAnnotation psiAnnotation, @NotNull PsiClass psiClass,
@@ -199,4 +199,23 @@ public abstract class AbstractClassProcessor extends AbstractProcessor implement
}
return result;
}
protected static boolean isAnyConstructorDefined(@NotNull PsiClass psiClass) {
return !filterToleratedElements(PsiClassUtil.collectClassConstructorIntern(psiClass)).isEmpty();
}
protected static boolean shouldGenerateConstructor(@NotNull PsiClass psiClass) {
// create the required constructor only if there are no other constructor annotations
if (!hasLombokConstructorAnnotations(psiClass)) {
return !isAnyConstructorDefined(psiClass);
}
return false;
}
protected static boolean hasLombokConstructorAnnotations(@NotNull PsiClass psiClass) {
return PsiAnnotationSearchUtil.isAnnotatedWith(psiClass, LombokClassNames.NO_ARGS_CONSTRUCTOR,
LombokClassNames.REQUIRED_ARGS_CONSTRUCTOR,
LombokClassNames.ALL_ARGS_CONSTRUCTOR,
LombokClassNames.BUILDER);
}
}
@@ -3,7 +3,6 @@ package de.plushnikov.intellij.plugin.processor.clazz;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import de.plushnikov.intellij.plugin.LombokClassNames;
import de.plushnikov.intellij.plugin.problem.ProblemProcessingSink;
import de.plushnikov.intellij.plugin.problem.ProblemSink;
import de.plushnikov.intellij.plugin.processor.LombokProcessorManager;
import de.plushnikov.intellij.plugin.processor.LombokPsiElementUsage;
@@ -19,7 +18,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static de.plushnikov.intellij.plugin.LombokClassNames.DATA;
/**
* @author Plushnikov Michail
@@ -98,8 +96,7 @@ public final class DataProcessor extends AbstractClassProcessor {
getEqualsAndHashCodeProcessor().validateCallSuperParamExtern(psiAnnotation, psiClass, builder);
}
final String staticName = getStaticConstructorNameValue(psiAnnotation);
if (shouldGenerateRequiredArgsConstructor(psiClass, staticName)) {
if (shouldGenerateConstructor(psiClass)) {
getRequiredArgsConstructorProcessor().validateBaseClassConstructor(psiClass, builder);
}
}
@@ -109,7 +106,7 @@ public final class DataProcessor extends AbstractClassProcessor {
private static void validateAnnotationOnRightType(@NotNull PsiClass psiClass, @NotNull ProblemSink builder) {
if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum() || psiClass.isRecord()) {
builder.addErrorMessage("inspection.message.data.only.supported.on.class.type")
.withLocalQuickFixes(() -> PsiQuickFixFactory.createDeleteAnnotationFix(psiClass, DATA));
.withLocalQuickFixes(() -> PsiQuickFixFactory.createDeleteAnnotationFix(psiClass, LombokClassNames.DATA));
builder.markFailed();
}
}
@@ -117,7 +114,8 @@ public final class DataProcessor extends AbstractClassProcessor {
@Override
protected void generatePsiElements(@NotNull PsiClass psiClass,
@NotNull PsiAnnotation psiAnnotation,
@NotNull List<? super PsiElement> target, @Nullable String nameHint) {
@NotNull List<? super PsiElement> target,
@Nullable String nameHint) {
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.GETTER)) {
target.addAll(getGetterProcessor().createFieldGetters(psiClass, PsiModifier.PUBLIC, nameHint));
}
@@ -133,43 +131,22 @@ public final class DataProcessor extends AbstractClassProcessor {
target.addAll(getToStringProcessor().createToStringMethod(psiClass, psiAnnotation));
}
final boolean hasConstructorWithoutParameters;
boolean hasConstructorWithoutParameters = false;
final String staticName = getStaticConstructorNameValue(psiAnnotation);
if (nameHint != null && !nameHint.equals(staticName) && !nameHint.equals(psiClass.getName())) return;
if (shouldGenerateRequiredArgsConstructor(psiClass, staticName)) {
if (!hasLombokConstructorAnnotations(psiClass) && PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.SUPER_BUILDER)) {
target.addAll(
getRequiredArgsConstructorProcessor().createRequiredArgsConstructor(psiClass, PsiModifier.PUBLIC, psiAnnotation, staticName, true));
// if there are no required field, it will already have a default constructor without parameters
// if there are no required fields, it will already have a default constructor without parameters
hasConstructorWithoutParameters = getRequiredArgsConstructorProcessor().getRequiredFields(psiClass).isEmpty();
}
else {
hasConstructorWithoutParameters = false;
}
if (!hasConstructorWithoutParameters && shouldGenerateExtraNoArgsConstructor(psiClass)) {
target.addAll(getNoArgsConstructorProcessor().createNoArgsConstructor(psiClass, PsiModifier.PRIVATE, psiAnnotation, true));
}
}
private static boolean shouldGenerateRequiredArgsConstructor(@NotNull PsiClass psiClass, @Nullable String staticName) {
boolean result = false;
// create required constructor only if there are no other constructor annotations
final boolean notAnnotatedWith = PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass,
LombokClassNames.NO_ARGS_CONSTRUCTOR,
LombokClassNames.REQUIRED_ARGS_CONSTRUCTOR,
LombokClassNames.ALL_ARGS_CONSTRUCTOR,
LombokClassNames.BUILDER,
LombokClassNames.SUPER_BUILDER);
if (notAnnotatedWith) {
final RequiredArgsConstructorProcessor requiredArgsConstructorProcessor = getRequiredArgsConstructorProcessor();
final Collection<PsiField> requiredFields = requiredArgsConstructorProcessor.getRequiredFields(psiClass);
result = requiredArgsConstructorProcessor.validateIsConstructorNotDefined(
psiClass, staticName, requiredFields, new ProblemProcessingSink());
}
return result;
}
@Override
public LombokPsiElementUsage checkFieldUsage(@NotNull PsiField psiField, @NotNull PsiAnnotation psiAnnotation) {
return LombokPsiElementUsage.READ_WRITE;
@@ -3,7 +3,6 @@ package de.plushnikov.intellij.plugin.processor.clazz;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import de.plushnikov.intellij.plugin.LombokClassNames;
import de.plushnikov.intellij.plugin.problem.ProblemProcessingSink;
import de.plushnikov.intellij.plugin.problem.ProblemSink;
import de.plushnikov.intellij.plugin.processor.LombokProcessorManager;
import de.plushnikov.intellij.plugin.processor.LombokPsiElementUsage;
@@ -13,7 +12,6 @@ import de.plushnikov.intellij.plugin.processor.clazz.constructor.NoArgsConstruct
import de.plushnikov.intellij.plugin.quickfix.PsiQuickFixFactory;
import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import de.plushnikov.intellij.plugin.util.PsiClassUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,8 +19,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static de.plushnikov.intellij.plugin.LombokClassNames.VALUE;
/**
* @author twillouer
*/
@@ -91,6 +87,10 @@ public final class ValueProcessor extends AbstractClassProcessor {
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.EQUALS_AND_HASHCODE)) {
getEqualsAndHashCodeProcessor().validateCallSuperParamExtern(psiAnnotation, psiClass, builder);
}
if (shouldGenerateConstructor(psiClass)) {
getAllArgsConstructorProcessor().validateBaseClassConstructor(psiClass, builder);
}
}
return builder.success();
}
@@ -98,7 +98,7 @@ public final class ValueProcessor extends AbstractClassProcessor {
private static void validateAnnotationOnRightType(@NotNull PsiClass psiClass, @NotNull ProblemSink builder) {
if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum() || psiClass.isRecord()) {
builder.addErrorMessage("inspection.message.value.only.supported.on.class.type")
.withLocalQuickFixes(() -> PsiQuickFixFactory.createDeleteAnnotationFix(psiClass, VALUE));
.withLocalQuickFixes(() -> PsiQuickFixFactory.createDeleteAnnotationFix(psiClass, LombokClassNames.VALUE));
builder.markFailed();
}
}
@@ -111,27 +111,23 @@ public final class ValueProcessor extends AbstractClassProcessor {
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.GETTER)) {
target.addAll(getGetterProcessor().createFieldGetters(psiClass, PsiModifier.PUBLIC, nameHint));
}
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.EQUALS_AND_HASHCODE)) {
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.EQUALS_AND_HASHCODE) &&
getEqualsAndHashCodeProcessor().noHintOrPossibleToGenerateElementNamed(nameHint, psiClass, psiAnnotation)) {
target.addAll(getEqualsAndHashCodeProcessor().createEqualAndHashCode(psiClass, psiAnnotation));
}
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.TO_STRING)) {
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.TO_STRING) &&
getToStringProcessor().noHintOrPossibleToGenerateElementNamed(nameHint, psiClass, psiAnnotation)) {
target.addAll(getToStringProcessor().createToStringMethod(psiClass, psiAnnotation));
}
// create required constructor only if there are no other constructor annotations
if (PsiAnnotationSearchUtil.isNotAnnotatedWith(psiClass, LombokClassNames.NO_ARGS_CONSTRUCTOR,
LombokClassNames.REQUIRED_ARGS_CONSTRUCTOR, LombokClassNames.ALL_ARGS_CONSTRUCTOR,
LombokClassNames.BUILDER)) {
PsiClassUtil.collectClassConstructorIntern(psiClass);
final String staticName = getStaticConstructorNameValue(psiAnnotation);
final String staticName = getStaticConstructorNameValue(psiAnnotation);
if (nameHint != null && !nameHint.equals(staticName) && !nameHint.equals(psiClass.getName())) return;
if (!hasLombokConstructorAnnotations(psiClass)) {
final Collection<PsiField> requiredFields = AbstractConstructorClassProcessor.getAllFields(psiClass);
if (getAllArgsConstructorProcessor().validateIsConstructorNotDefined(psiClass, staticName, requiredFields,
new ProblemProcessingSink())) {
target.addAll(
getAllArgsConstructorProcessor().createAllArgsConstructor(psiClass, PsiModifier.PUBLIC, psiAnnotation, staticName, requiredFields,
true));
}
target.addAll(
getAllArgsConstructorProcessor().createAllArgsConstructor(psiClass, PsiModifier.PUBLIC, psiAnnotation, staticName, requiredFields,
true));
}
if (shouldGenerateExtraNoArgsConstructor(psiClass)) {
@@ -300,7 +300,7 @@ public abstract class AbstractConstructorClassProcessor extends AbstractClassPro
final String constructorVisibility = staticConstructorRequired || psiClass.isEnum() ? PsiModifier.PRIVATE : methodModifier;
// check, if we should skip verification for presence of any (not Tolerated) constructors
// check if we should skip verification for presence of any (not Tolerated) constructors
if (!skipConstructorIfAnyConstructorExists || !isAnyConstructorDefined(psiClass)) {
boolean hasConstructor = !validateIsConstructorNotDefined(psiClass,
useJavaDefaults ? Collections.emptyList() : params,
@@ -319,12 +319,6 @@ public abstract class AbstractConstructorClassProcessor extends AbstractClassPro
return methods;
}
private static boolean isAnyConstructorDefined(@NotNull PsiClass psiClass) {
Collection<PsiMethod> constructors = PsiClassUtil.collectClassConstructorIntern(psiClass);
return ContainerUtil.exists(constructors,
psiMethod -> PsiAnnotationSearchUtil.isNotAnnotatedWith(psiMethod, LombokClassNames.TOLERATE));
}
private PsiMethod createConstructor(@NotNull PsiClass psiClass, @PsiModifier.ModifierConstant @NotNull String modifier,
boolean useJavaDefaults, @NotNull Collection<PsiField> params, @NotNull PsiAnnotation psiAnnotation) {
LombokLightMethodBuilder constructorBuilder = new LombokLightMethodBuilder(psiClass.getManager(), getConstructorName(psiClass))
@@ -1,10 +1,10 @@
package de.plushnikov.intellij.plugin.highlights;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.testFramework.LightProjectDescriptor;
import com.siyeh.ig.LightJavaInspectionTestCase;
import de.plushnikov.intellij.plugin.LombokTestUtil;
import de.plushnikov.intellij.plugin.inspection.LombokInspection;
import org.jetbrains.annotations.NotNull;
@@ -13,13 +13,6 @@ import org.jetbrains.annotations.NotNull;
*/
public abstract class AbstractLombokHighlightsTest extends LightJavaInspectionTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
Registry.get("platform.random.idempotence.check.rate").setValue(1, getTestRootDisposable());
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
@@ -33,7 +26,7 @@ public abstract class AbstractLombokHighlightsTest extends LightJavaInspectionTe
@Override
protected InspectionProfileEntry getInspection() {
return null;
return new LombokInspection();
}
}
@@ -0,0 +1,20 @@
package de.plushnikov.intellij.plugin.highlights;
/**
* Test for highlighting issues with @Data and @Value annotations in inheritance scenarios
*/
public class DataValueInheritanceHighlightTest extends AbstractLombokHighlightsTest {
@Override
protected String getBasePath() {
return super.getBasePath() + "/dataValueInheritance";
}
public void testDataInheritanceHighlighting() {
doTest();
}
public void testValueInheritanceHighlighting() {
doTest();
}
}
@@ -1,8 +1,5 @@
package de.plushnikov.intellij.plugin.highlights;
import com.intellij.codeInspection.InspectionProfileEntry;
import de.plushnikov.intellij.plugin.inspection.LombokInspection;
public class LombokInspectionHighlightTest extends AbstractLombokHighlightsTest {
@Override
@@ -10,11 +7,6 @@ public class LombokInspectionHighlightTest extends AbstractLombokHighlightsTest
return super.getBasePath() + "/builderDefault";
}
@Override
protected InspectionProfileEntry getInspection() {
return new LombokInspection();
}
public void testBuilderDefaultWithoutBuilderAnnotation() {
doTest();
}
@@ -1,8 +1,5 @@
package de.plushnikov.intellij.plugin.highlights;
import com.intellij.codeInspection.InspectionProfileEntry;
import de.plushnikov.intellij.plugin.inspection.LombokInspection;
public class RecordsHighlightTest extends AbstractLombokHighlightsTest {
@Override
@@ -10,11 +7,6 @@ public class RecordsHighlightTest extends AbstractLombokHighlightsTest {
return super.getBasePath() + "/onRecord";
}
@Override
protected InspectionProfileEntry getInspection() {
return new LombokInspection();
}
public void testInvalidLombokAnnotationsOnRecord() {
doTest();
}
@@ -1,8 +1,5 @@
package de.plushnikov.intellij.plugin.highlights;
import com.intellij.codeInspection.InspectionProfileEntry;
import de.plushnikov.intellij.plugin.inspection.LombokInspection;
public class SneakyThrowsHighlightTest extends AbstractLombokHighlightsTest {
@Override
@@ -10,11 +7,6 @@ public class SneakyThrowsHighlightTest extends AbstractLombokHighlightsTest {
return super.getBasePath() + "/sneakyThrows";
}
@Override
protected InspectionProfileEntry getInspection() {
return new LombokInspection();
}
public void testSneakThrowsDoesntCatchCaughtException() {
doTest();
}
@@ -1,8 +1,5 @@
package de.plushnikov.intellij.plugin.highlights;
import com.intellij.codeInspection.InspectionProfileEntry;
import de.plushnikov.intellij.plugin.inspection.LombokInspection;
public class SuperBuilderHighlightTest extends AbstractLombokHighlightsTest {
@Override
@@ -10,11 +7,6 @@ public class SuperBuilderHighlightTest extends AbstractLombokHighlightsTest {
return super.getBasePath() + "/superBuilder";
}
@Override
protected InspectionProfileEntry getInspection() {
return new LombokInspection();
}
public void testSuperBuilderOnInnerClass() {
doTest();
}
@@ -0,0 +1,31 @@
import lombok.Data;
import lombok.EqualsAndHashCode;
public class DataInheritanceHighlighting {
@Data
@EqualsAndHashCode(callSuper = true)
static class WithConstructor extends ParentWithNonDefaultConstructor {
private String field;
// Explicitly define constructor to call parent constructor
WithConstructor() {
super("defaultValue");
}
}
<error descr="Lombok needs a default constructor in the base class">@Data</error>
@EqualsAndHashCode(callSuper = true)
static class WithoutConstructor extends ParentWithNonDefaultConstructor {
private String field;
// No constructor defined - should show error
}
static class ParentWithNonDefaultConstructor {
private final String requiredField;
ParentWithNonDefaultConstructor(String requiredField) {
this.requiredField = requiredField;
}
}
}
@@ -0,0 +1,32 @@
import lombok.Value;
import lombok.EqualsAndHashCode;
public class ValueInheritanceHighlighting {
@Value
@EqualsAndHashCode(callSuper = true)
static class WithConstructor extends ParentWithNonDefaultConstructor {
String field;
// Explicitly define constructor to call parent constructor
WithConstructor(String field) {
super("defaultValue");
this.field = field;
}
}
<error descr="Lombok needs a default constructor in the base class">@Value</error>
@EqualsAndHashCode(callSuper = true)
static class WithoutConstructor extends ParentWithNonDefaultConstructor {
String field;
// No constructor defined - should show error
}
static class ParentWithNonDefaultConstructor {
private final String requiredField;
ParentWithNonDefaultConstructor(String requiredField) {
this.requiredField = requiredField;
}
}
}