[lombok] IDEA-255688 Using existing functionality to skip checking expressions from field initializer

and to skip checking field initializer expression

GitOrigin-RevId: 67211ecbad65889dabe6385678cc1b7c47a96deb
This commit is contained in:
Michail Plushnikov
2023-12-05 19:35:04 +01:00
committed by intellij-monorepo-bot
parent 7b19a2dd37
commit 4222be044e
11 changed files with 47 additions and 136 deletions

View File

@@ -34,8 +34,6 @@
<extensionPoint qualifiedName="com.intellij.codeInsight.changeVariableTypeQuickFixProvider" interface="com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.codeInsight.changeVariableTypeQuickFixProvider" interface="com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.lang.jvm.annotationPackageSupport" interface="com.intellij.codeInsight.annoPackages.AnnotationPackageSupport" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.lang.jvm.annotationPackageSupport" interface="com.intellij.codeInsight.annoPackages.AnnotationPackageSupport" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.lang.jvm.ignoreAnnotationParamSupport" interface="com.intellij.codeInspection.DefaultAnnotationParamInspection$IgnoreAnnotationParamSupport" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.lang.jvm.ignoreAnnotationParamSupport" interface="com.intellij.codeInspection.DefaultAnnotationParamInspection$IgnoreAnnotationParamSupport" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.lang.jvm.ignoreVariableInitializedBeforeUsageSupport" interface="com.intellij.codeInsight.daemon.impl.analysis.VariableInitializedBeforeUsageSupport" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.lang.jvm.ignoreMethodCallExpressionNPESupport" interface="com.intellij.codeInspection.dataFlow.MethodCallProduceNPESupport" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.javaLanguageLevelPusherCustomizer" interface="com.intellij.openapi.roots.impl.JavaLanguageLevelPusherCustomizer" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.javaLanguageLevelPusherCustomizer" interface="com.intellij.openapi.roots.impl.JavaLanguageLevelPusherCustomizer" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.propertyAccessorDetector" interface="com.intellij.psi.util.PropertyAccessorDetector" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.propertyAccessorDetector" interface="com.intellij.psi.util.PropertyAccessorDetector" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.virtualManifestProvider" interface="com.intellij.codeInsight.daemon.impl.analysis.VirtualManifestProvider" dynamic="true"/> <extensionPoint qualifiedName="com.intellij.virtualManifestProvider" interface="com.intellij.codeInsight.daemon.impl.analysis.VirtualManifestProvider" dynamic="true"/>

View File

@@ -18,6 +18,7 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.LanguageLevel; import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.controlFlow.*; import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.light.LightRecordCanonicalConstructor; import com.intellij.psi.impl.light.LightRecordCanonicalConstructor;
import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.LocalSearchScope;
@@ -318,10 +319,6 @@ public final class HighlightControlFlowUtil {
boolean ignoreFinality) { boolean ignoreFinality) {
if (variable instanceof ImplicitVariable) return null; if (variable instanceof ImplicitVariable) return null;
if (!PsiUtil.isAccessedForReading(expression)) return null; if (!PsiUtil.isAccessedForReading(expression)) return null;
if (ContainerUtil.exists(VariableInitializedBeforeUsageSupport.EP_NAME.getExtensionList(),
ext -> ext.ignoreVariableExpression(expression, variable))) {
return null;
}
int startOffset = expression.getTextRange().getStartOffset(); int startOffset = expression.getTextRange().getStartOffset();
PsiElement topBlock; PsiElement topBlock;
if (variable.hasInitializer()) { if (variable.hasInitializer()) {
@@ -394,6 +391,9 @@ public final class HighlightControlFlowUtil {
isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) { isFieldInitializedInClassInitializer(field, true, aClass.getInitializers())) {
return null; return null;
} }
if(anotherField!=null && anotherField.hasInitializer() && !PsiAugmentProvider.canTrustFieldInitializer(anotherField)) {
return null;
}
int offset = startOffset; int offset = startOffset;
if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) { if (anotherField != null && anotherField.getContainingClass() == aClass && !field.hasModifierProperty(PsiModifier.STATIC)) {

View File

@@ -1,26 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiVariable;
import org.jetbrains.annotations.NotNull;
/**
* Allows skipping 'Variable might not have been initialized' highlighting for specific PsiReference
*/
public interface VariableInitializedBeforeUsageSupport {
ExtensionPointName<VariableInitializedBeforeUsageSupport> EP_NAME =
ExtensionPointName.create("com.intellij.lang.jvm.ignoreVariableInitializedBeforeUsageSupport");
/**
* Checks if the given expression should be ignored for inspection.
*
* @param psiExpression the expression to be checked for ignoring the initializer
* @param psiVariable the variable from the expression resolving
* @return true if the inspection should be skipped for the {@code psiExpression},
* otherwise false
*/
default boolean ignoreVariableExpression(@NotNull PsiReferenceExpression psiExpression, @NotNull PsiVariable psiVariable) {
return false;
}
}

View File

@@ -1,26 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.dataFlow;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.psi.PsiExpression;
import org.jetbrains.annotations.NotNull;
/**
* This interface provides support for checking whether a method call expression should be ignored
* for inspection that checks for potential null pointer exceptions (NPE).
*/
public interface MethodCallProduceNPESupport {
ExtensionPointName<MethodCallProduceNPESupport> EP_NAME =
ExtensionPointName.create("com.intellij.lang.jvm.ignoreMethodCallExpressionNPESupport");
/**
* Checks if the given expression should be ignored for inspection.
*
* @param psiExpression expression which can actually violates the nullability
* @return true if the inspection should be skipped for the {@code psiExpression},
* otherwise false
*/
default boolean ignoreMethodCallExpression(@NotNull PsiExpression psiExpression) {
return false;
}
}

View File

@@ -181,10 +181,6 @@ public final class NullabilityProblemKind<T extends PsiElement> {
if (parameter != null) { if (parameter != null) {
return getParameterProblem(parameter, expression, context); return getParameterProblem(parameter, expression, context);
} }
if (ContainerUtil.exists(MethodCallProduceNPESupport.EP_NAME.getExtensionList(),
ext -> ext.ignoreMethodCallExpression(expression))) {
return null;
}
return callNPE.problem((PsiMethodCallExpression)grandParent, expression); return callNPE.problem((PsiMethodCallExpression)grandParent, expression);
} }
return fieldAccessNPE.problem(context, expression); return fieldAccessNPE.problem(context, expression);

View File

@@ -37,6 +37,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaControlTransferValue.Trap;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.pom.java.LanguageLevel; import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.impl.source.tree.java.PsiEmptyExpressionImpl; import com.intellij.psi.impl.source.tree.java.PsiEmptyExpressionImpl;
import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.InheritanceUtil;
@@ -101,7 +102,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new FlushFieldsInstruction()); addInstruction(new FlushFieldsInstruction());
} }
for (PsiElement element = psiClass.getFirstChild(); element != null; element = element.getNextSibling()) { for (PsiElement element = psiClass.getFirstChild(); element != null; element = element.getNextSibling()) {
if ((element instanceof PsiField field && field.hasInitializer() || element instanceof PsiClassInitializer) && if ((element instanceof PsiField field &&
field.hasInitializer() && PsiAugmentProvider.canTrustFieldInitializer(field) || element instanceof PsiClassInitializer) &&
((PsiMember)element).hasModifierProperty(PsiModifier.STATIC) == isStatic) { ((PsiMember)element).hasModifierProperty(PsiModifier.STATIC) == isStatic) {
element.accept(this); element.accept(this);
} }

View File

@@ -5,6 +5,7 @@ import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.augment.PsiExtensionMethod; import com.intellij.psi.augment.PsiExtensionMethod;
import com.intellij.psi.impl.source.PsiExtensibleClass; import com.intellij.psi.impl.source.PsiExtensibleClass;
import com.siyeh.ig.psiutils.InitializationUtils;
import de.plushnikov.intellij.plugin.LombokClassNames; import de.plushnikov.intellij.plugin.LombokClassNames;
import de.plushnikov.intellij.plugin.processor.LombokProcessorManager; import de.plushnikov.intellij.plugin.processor.LombokProcessorManager;
import de.plushnikov.intellij.plugin.processor.Processor; import de.plushnikov.intellij.plugin.processor.Processor;
@@ -12,6 +13,7 @@ import de.plushnikov.intellij.plugin.processor.ValProcessor;
import de.plushnikov.intellij.plugin.processor.method.ExtensionMethodsHelper; import de.plushnikov.intellij.plugin.processor.method.ExtensionMethodsHelper;
import de.plushnikov.intellij.plugin.processor.modifier.ModifierProcessor; import de.plushnikov.intellij.plugin.processor.modifier.ModifierProcessor;
import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -61,13 +63,40 @@ public class LombokAugmentProvider extends PsiAugmentProvider {
/* /*
* The final fields that are marked with Builder.Default contains only possible value * The final fields that are marked with Builder.Default contains only possible value
* because user can set another value during the creation of the object. * because user can set another value during the creation of the object.
*
* The fields marked with Getter(lazy=true) contains a value that will be calculated only at first access to the getter-Method
*/ */
//see de.plushnikov.intellij.plugin.inspection.DataFlowInspectionTest.testDefaultBuilderFinalValueInspectionIsAlwaysThat //see de.plushnikov.intellij.plugin.inspection.DataFlowInspectionTest.testDefaultBuilderFinalValueInspectionIsAlwaysThat
//see de.plushnikov.intellij.plugin.inspection.PointlessBooleanExpressionInspectionTest.testPointlessBooleanExpressionBuilderDefault //see de.plushnikov.intellij.plugin.inspection.PointlessBooleanExpressionInspectionTest.testPointlessBooleanExpressionBuilderDefault
//see com.intellij.java.lomboktest.LombokHighlightingTest.testBuilderWithDefaultRedundantInitializer //see com.intellij.java.lomboktest.LombokHighlightingTest.testBuilderWithDefaultRedundantInitializer
//see com.intellij.java.lomboktest.LombokHighlightingTest.testGetterLazyInvocationProduceNPE
//see com.intellij.java.lomboktest.LombokHighlightingTest.testGetterLazyVariableNotInitialized
@Override @Override
protected boolean fieldInitializerMightBeChanged(@NotNull PsiField field) { protected boolean fieldInitializerMightBeChanged(@NotNull PsiField field) {
return PsiAnnotationSearchUtil.isAnnotatedWith(field, LombokClassNames.BUILDER_DEFAULT); if (field.hasAnnotation(LombokClassNames.BUILDER_DEFAULT)) {
return true;
}
final PsiAnnotation getterAnnotation = PsiAnnotationSearchUtil.findAnnotation(field, LombokClassNames.GETTER);
final boolean isLazyGetter = null != getterAnnotation &&
PsiAnnotationUtil.getBooleanAnnotationValue(getterAnnotation, "lazy", false);
if (isLazyGetter) {
final PsiExpression fieldInitializer = field.getInitializer();
if (fieldInitializer instanceof PsiMethodCallExpression methodCallExpression) {
final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiReferenceExpression qualifierReferenceExpression) {
final PsiElement referencedElement = qualifierReferenceExpression.resolve();
if (referencedElement instanceof PsiField referencedField) {
final PsiClass containingClass = referencedField.getContainingClass();
if (containingClass != null) {
return InitializationUtils.isInitializedInConstructors(referencedField, containingClass);
}
}
}
}
}
return isLazyGetter;
} }
@Nullable @Nullable
@@ -91,11 +120,11 @@ public class LombokAugmentProvider extends PsiAugmentProvider {
final List<Psi> emptyResult = Collections.emptyList(); final List<Psi> emptyResult = Collections.emptyList();
if ((type != PsiClass.class && type != PsiField.class && type != PsiMethod.class) || !(element instanceof PsiExtensibleClass) if ((type != PsiClass.class && type != PsiField.class && type != PsiMethod.class) || !(element instanceof PsiExtensibleClass)
|| (element instanceof PsiCompiledElement) // skip compiled classes || (element instanceof PsiCompiledElement) // skip compiled classes
) { ) {
return emptyResult; return emptyResult;
} }
final PsiClass psiClass = (PsiClass) element; final PsiClass psiClass = (PsiClass)element;
if (!psiClass.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { if (!psiClass.getLanguage().isKindOf(JavaLanguage.INSTANCE)) {
return emptyResult; return emptyResult;
} }
@@ -120,7 +149,7 @@ public class LombokAugmentProvider extends PsiAugmentProvider {
for (Processor processor : LombokProcessorManager.getProcessors(type)) { for (Processor processor : LombokProcessorManager.getProcessors(type)) {
final List<? super PsiElement> generatedElements = processor.process(psiClass, nameHint); final List<? super PsiElement> generatedElements = processor.process(psiClass, nameHint);
for (Object psiElement : generatedElements) { for (Object psiElement : generatedElements) {
result.add((Psi) psiElement); result.add((Psi)psiElement);
} }
} }
return result; return result;

View File

@@ -1,39 +0,0 @@
package de.plushnikov.intellij.plugin.provider;
import com.intellij.codeInspection.dataFlow.MethodCallProduceNPESupport;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.ig.psiutils.InitializationUtils;
import de.plushnikov.intellij.plugin.LombokClassNames;
import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import org.jetbrains.annotations.NotNull;
public class LombokMethodCallExpressionNPESupport implements MethodCallProduceNPESupport {
@Override
public boolean ignoreMethodCallExpression(@NotNull PsiExpression psiExpression) {
final PsiField field = PsiTreeUtil.getParentOfType(psiExpression, PsiField.class);
if (field == null) {
return false;
}
final PsiAnnotation getterAnnotation = PsiAnnotationSearchUtil.findAnnotation(field, LombokClassNames.GETTER);
final boolean isLazyGetter = null != getterAnnotation && PsiAnnotationUtil.getBooleanAnnotationValue(getterAnnotation, "lazy", false);
if (isLazyGetter) {
final PsiReference reference = psiExpression.getReference();
if (reference != null) {
PsiElement referencedElement = reference.resolve();
if (!(referencedElement instanceof PsiField referencedField)) {
return false;
}
PsiClass containingClass = referencedField.getContainingClass();
if (containingClass != null) {
return InitializationUtils.isInitializedInConstructors(referencedField, containingClass);
}
}
}
return isLazyGetter;
}
}

View File

@@ -1,26 +0,0 @@
package de.plushnikov.intellij.plugin.provider;
import com.intellij.codeInsight.daemon.impl.analysis.VariableInitializedBeforeUsageSupport;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import de.plushnikov.intellij.plugin.LombokClassNames;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import org.jetbrains.annotations.NotNull;
/**
* A class that implements the VariableInitializedBeforeUsageSupport interface to provide support for Lombok annotated variables.
* It checks if a variable expression should be ignored based on Lombok annotations.
*/
public class LombokVariableInitializedBeforeUsageSupport implements VariableInitializedBeforeUsageSupport {
@Override
public boolean ignoreVariableExpression(@NotNull PsiReferenceExpression psiExpression, @NotNull PsiVariable psiVariable) {
final PsiField field = PsiTreeUtil.getParentOfType(psiExpression, PsiField.class);
if (field == null) {
return false;
}
final PsiAnnotation getterAnnotation = field.getAnnotation(LombokClassNames.GETTER);
return null != getterAnnotation && PsiAnnotationUtil.getBooleanAnnotationValue(getterAnnotation, "lazy", false);
}
}

View File

@@ -53,8 +53,6 @@
<lang.psiAugmentProvider implementation="de.plushnikov.intellij.plugin.provider.LombokAugmentProvider"/> <lang.psiAugmentProvider implementation="de.plushnikov.intellij.plugin.provider.LombokAugmentProvider"/>
<lang.jvm.annotationPackageSupport implementation="de.plushnikov.intellij.plugin.provider.LombokAnnotationSupport"/> <lang.jvm.annotationPackageSupport implementation="de.plushnikov.intellij.plugin.provider.LombokAnnotationSupport"/>
<lang.jvm.ignoreAnnotationParamSupport implementation="de.plushnikov.intellij.plugin.provider.LombokDefaultAnnotationParamSupport"/> <lang.jvm.ignoreAnnotationParamSupport implementation="de.plushnikov.intellij.plugin.provider.LombokDefaultAnnotationParamSupport"/>
<lang.jvm.ignoreVariableInitializedBeforeUsageSupport implementation="de.plushnikov.intellij.plugin.provider.LombokVariableInitializedBeforeUsageSupport"/>
<lang.jvm.ignoreMethodCallExpressionNPESupport implementation="de.plushnikov.intellij.plugin.provider.LombokMethodCallExpressionNPESupport"/>
<implicitUsageProvider implementation="de.plushnikov.intellij.plugin.provider.LombokImplicitUsageProvider"/> <implicitUsageProvider implementation="de.plushnikov.intellij.plugin.provider.LombokImplicitUsageProvider"/>
<projectConfigurable groupId="language" <projectConfigurable groupId="language"
key="plugin.settings.title" bundle="messages.LombokBundle" key="plugin.settings.title" bundle="messages.LombokBundle"

View File

@@ -14,6 +14,7 @@ public class GetterLazyInvocationProduceNPE {
} }
private Bar <warning descr="Field 'bar' may be 'final'">bar</warning>; private Bar <warning descr="Field 'bar' may be 'final'">bar</warning>;
private Bar <warning descr="Private field 'bar2' is never assigned">bar2</warning>;
private Car car; private Car car;
public GetterLazyInvocationProduceNPE(Bar bar, Car car) { public GetterLazyInvocationProduceNPE(Bar bar, Car car) {
@@ -21,11 +22,15 @@ public class GetterLazyInvocationProduceNPE {
this.car = car; this.car = car;
} }
// without warning // without warning, because of lazy getter and initialized in constructor
@Getter(lazy = true) @Getter(lazy = true)
private final String barString = bar.sayHello(); private final String barString = bar.sayHello();
//with warning! // with warning, because of lazy getter and NOT initialized in constructor
@Getter(lazy = true)
private final String bar2String = bar2.<warning descr="Method invocation 'sayHello' will produce 'NullPointerException'">sayHello</warning>();
//with warning, because of NOT lazy getter
@Getter @Getter
private final String carString = car.<warning descr="Method invocation 'sayHello' will produce 'NullPointerException'">sayHello</warning>(); private final String carString = car.<warning descr="Method invocation 'sayHello' will produce 'NullPointerException'">sayHello</warning>();