[java-inspections] Support simple chaining in NotNullFieldNotInitializedInspection

Fixes IDEA-145194 Do not report 'Not-null fields must be initialized' when field is initialized indirectly

GitOrigin-RevId: c3bae51deaedbbbb4d035e5d7d3b55f2ca9b8c03
This commit is contained in:
Tagir Valeev
2024-09-11 14:30:40 +02:00
committed by intellij-monorepo-bot
parent 41c0081fd8
commit c22a9186f9
3 changed files with 79 additions and 3 deletions

View File

@@ -19,6 +19,9 @@ import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.psi.*;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@@ -28,7 +31,9 @@ import static com.intellij.codeInspection.options.OptPane.checkbox;
import static com.intellij.codeInspection.options.OptPane.pane;
public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalInspectionTool {
@Language("jvm-field-name")
private static final String IGNORE_IMPLICITLY_WRITTEN_FIELDS_NAME = "IGNORE_IMPLICITLY_WRITTEN_FIELDS";
@Language("jvm-field-name")
private static final String IGNORE_FIELDS_WRITTEN_IN_SETUP_NAME = "IGNORE_FIELDS_WRITTEN_IN_SETUP";
public boolean IGNORE_IMPLICITLY_WRITTEN_FIELDS = true;
public boolean IGNORE_FIELDS_WRITTEN_IN_SETUP = true;
@@ -49,9 +54,10 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
public void visitField(@NotNull PsiField field) {
NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(field);
if (info == null ||
info.getNullability() != Nullability.NOT_NULL ||
HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) {
if (info == null || info.getNullability() != Nullability.NOT_NULL) return;
if (HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field) ||
isWrittenIndirectly(field)) {
return;
}
@@ -95,6 +101,39 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
};
}
private static boolean isWrittenIndirectly(@NotNull PsiField field) {
PsiClass fieldClass = field.getContainingClass();
if (fieldClass == null) return false;
PsiMethod[] constructors = fieldClass.getConstructors();
if (constructors.length == 0) return false;
return ContainerUtil.all(constructors, constructor ->
JavaPsiConstructorUtil.isChainedConstructorCall(JavaPsiConstructorUtil.findThisOrSuperCallInConstructor(constructor)) ||
isWrittenIndirectlyIn(field, constructor));
}
private static boolean isWrittenIndirectlyIn(@NotNull PsiField field, @NotNull PsiMethod constructor) {
PsiCodeBlock body = constructor.getBody();
if (body == null) return false;
PsiStatement[] statements = body.getStatements();
for (PsiStatement statement : statements) {
if (statement instanceof PsiExpressionStatement expressionStatement &&
expressionStatement.getExpression() instanceof PsiMethodCallExpression call) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null || qualifier instanceof PsiThisExpression thisExpression && thisExpression.getQualifier() == null) {
PsiMethod target = call.resolveMethod();
if (target != null && !target.hasModifierProperty(PsiModifier.STATIC) &&
target.getContainingClass() == constructor.getContainingClass() && !target.isConstructor()) {
PsiCodeBlock targetBody = target.getBody();
if (targetBody != null && HighlightControlFlowUtil.variableDefinitelyAssignedIn(field, targetBody)) {
return true;
}
}
}
}
}
return false;
}
protected void reportProblem(@NotNull ProblemsHolder holder,
PsiElement anchor,
@InspectionMessage String message,

View File

@@ -0,0 +1,36 @@
import org.jetbrains.annotations.*;
class Test {
@NotNull String str1;
<warning descr="Not-null fields must be initialized">@NotNull</warning> String str2;
@NotNull String str3;
<warning descr="Not-null fields must be initialized">@NotNull</warning> String str4;
Test() {
init();
this.initStr3();
new Test().initStr4();
}
Test(String s) {
this();
str2 = s;
}
void initStr3() {
str3 = "";
}
void initStr4() {
str4 = "";
}
void init() {
if (Math.random() > 0.5) {
str1 = "";
str2 = "";
} else {
str1 = "123";
}
}
}

View File

@@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
public class NotNullFieldNotInitializedInspectionTest extends LightJavaCodeInsightFixtureTestCase {
public void testNotNullFieldNotInitialized() { doTest(); }
public void testNotNullFieldInitializedIndirectly() { doTest(); }
public void testNotNullFieldInitializedInLambda() { doTest(); }
public void testNotNullFieldNotInitializedInOneConstructor() { doTest(); }
public void testTypeUseNotNullField() {