mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-218253 Extract "Not-null field is not initialized" to separate inspection and improve it
GitOrigin-RevId: 0f709e869db16fe75318864e45bda2bed6b9e4f6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6f6ef13cd3
commit
6ebd80cf9f
+3
-25
@@ -3,7 +3,6 @@ package com.intellij.codeInspection.nullable;
|
||||
|
||||
import com.intellij.codeInsight.*;
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
||||
import com.intellij.codeInspection.*;
|
||||
@@ -50,7 +49,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
@Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL = true;
|
||||
@SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_GETTER = true;
|
||||
@SuppressWarnings({"WeakerAccess"}) public boolean IGNORE_EXTERNAL_SUPER_NOTNULL;
|
||||
@SuppressWarnings({"WeakerAccess"}) public boolean REQUIRE_NOTNULL_FIELDS_INITIALIZED = true;
|
||||
@SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED;
|
||||
@Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_NOT_ANNOTATED_SETTER_PARAMETER = true;
|
||||
@Deprecated @SuppressWarnings({"WeakerAccess"}) public boolean REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = true; // remains for test
|
||||
@@ -119,10 +117,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
|
||||
checkConstructorParameters(field, annotated, manager, anno, annoToRemove, holder);
|
||||
}
|
||||
|
||||
if (REQUIRE_NOTNULL_FIELDS_INITIALIZED && !annotated.isDeclaredNullable) {
|
||||
checkNotNullFieldsInitialized(field, manager, holder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -283,7 +277,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
private void checkCollectionNullityOnAssignment(@NotNull PsiElement errorElement,
|
||||
@Nullable PsiType expectedType,
|
||||
@Nullable PsiType assignedType) {
|
||||
if (isNullableNotNullCollectionConflict(errorElement, expectedType, assignedType, new HashSet<>())) {
|
||||
if (isNullableNotNullCollectionConflict(expectedType, assignedType, new HashSet<>())) {
|
||||
holder.registerProblem(errorElement,
|
||||
"Assigning a collection of nullable elements into a collection of non-null elements",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
||||
@@ -291,8 +285,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNullableNotNullCollectionConflict(PsiElement place,
|
||||
@Nullable PsiType expectedType,
|
||||
private boolean isNullableNotNullCollectionConflict(@Nullable PsiType expectedType,
|
||||
@Nullable PsiType assignedType,
|
||||
@NotNull Set<? super Couple<PsiType>> visited) {
|
||||
if (!visited.add(Couple.of(expectedType, assignedType))) return false;
|
||||
@@ -307,7 +300,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
PsiType expectedArg = PsiUtil.substituteTypeParameter(expectedType, CommonClassNames.JAVA_UTIL_MAP, i, false);
|
||||
PsiType assignedArg = PsiUtil.substituteTypeParameter(assignedType, CommonClassNames.JAVA_UTIL_MAP, i, false);
|
||||
if (isNullityConflict(expectedArg, assignedArg) ||
|
||||
expectedArg != null && assignedArg != null && isNullableNotNullCollectionConflict(place, expectedArg, assignedArg, visited)) {
|
||||
expectedArg != null && assignedArg != null && isNullableNotNullCollectionConflict(expectedArg, assignedArg, visited)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -502,21 +495,6 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
|
||||
LOG.assertTrue(parameter.isPhysical(), setter.getText());
|
||||
}
|
||||
|
||||
private static void checkNotNullFieldsInitialized(PsiField field, NullableNotNullManager manager, @NotNull ProblemsHolder holder) {
|
||||
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(field);
|
||||
if (info == null ||
|
||||
info.getNullability() != Nullability.NOT_NULL ||
|
||||
HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean byDefault = info.isContainer();
|
||||
PsiAnnotation annotation = info.getAnnotation();
|
||||
PsiJavaCodeReferenceElement name = annotation.getNameReferenceElement();
|
||||
holder.registerProblem(annotation.isPhysical() && !byDefault ? annotation : field.getNameIdentifier(),
|
||||
(byDefault && name != null ? "@" + name.getReferenceName() : "Not-null") + " fields must be initialized");
|
||||
}
|
||||
|
||||
private void checkConstructorParameters(PsiField field,
|
||||
Annotated annotated,
|
||||
NullableNotNullManager manager,
|
||||
|
||||
@@ -276,7 +276,7 @@ convert.to.string.text=Convert to string literal
|
||||
convert.to.raw.string.text=Convert to raw string literal
|
||||
|
||||
initialize.final.field.in.constructor.name=Initialize in constructor
|
||||
initialize.final.field.in.constructor.choose.dialog.title=Choose constructors to add initialization to
|
||||
initialize.final.field.in.constructor.choose.dialog.title=Choose Constructors to Add Initialization to
|
||||
|
||||
remove.redundant.arguments.text=Remove redundant arguments to call ''{0}''
|
||||
remove.redundant.arguments.family=Remove redundant arguments
|
||||
|
||||
@@ -1506,9 +1506,12 @@
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="WrongPackageStatement" bundle="messages.InspectionsBundle" key="wrong.package.statement" groupKey="group.names.probable.bugs"
|
||||
enabledByDefault="true" level="ERROR"
|
||||
implementationClass="com.intellij.codeInspection.wrongPackageStatement.WrongPackageStatementInspection" />
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="NullableProblems" bundle="messages.InspectionsBundle" key="inspection.nullable.problems.display.name"
|
||||
groupKey="group.names.probable.bugs" enabledByDefault="true" level="WARNING"
|
||||
<localInspection groupPath="Java,Probable bugs" language="JAVA" shortName="NullableProblems" bundle="messages.InspectionsBundle" key="inspection.nullable.problems.display.name"
|
||||
groupKey="group.names.nullability.problems" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.nullable.NullableStuffInspection" />
|
||||
<localInspection groupPath="Java,Probable bugs" language="JAVA" shortName="NotNullFieldNotInitialized" bundle="messages.InspectionsBundle" key="inspection.notnull.field.not.initialized.display.name"
|
||||
groupKey="group.names.nullability.problems" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.nullable.NotNullFieldNotInitializedInspection" />
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="ParameterCanBeLocal" key="inspection.parameter.can.be.local.display.name"
|
||||
groupKey="group.names.class.structure" enabledByDefault="true" level="WARNING" bundle="messages.InspectionsBundle"
|
||||
implementationClass="com.intellij.codeInspection.varScopeCanBeNarrowed.ParameterCanBeLocalInspection" />
|
||||
|
||||
+30
-17
@@ -2,12 +2,14 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInsight.lookup.ExpressionLookupItem;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.template.*;
|
||||
import com.intellij.codeInsight.template.PsiElementResult;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@@ -17,7 +19,6 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -28,18 +29,18 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AddVariableInitializerFix implements IntentionAction {
|
||||
public class AddVariableInitializerFix extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.AddReturnFix");
|
||||
private final PsiVariable myVariable;
|
||||
|
||||
public AddVariableInitializerFix(@NotNull PsiVariable variable) {
|
||||
myVariable = variable;
|
||||
super(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getText() {
|
||||
return CodeInsightBundle.message("quickfix.add.variable.text", myVariable.getName());
|
||||
PsiVariable variable = ObjectUtils.tryCast(myStartElement.getElement(), PsiVariable.class);
|
||||
return variable == null ? getFamilyName() : CodeInsightBundle.message("quickfix.add.variable.text", variable.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,29 +50,40 @@ public class AddVariableInitializerFix implements IntentionAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
return myVariable.isValid() &&
|
||||
BaseIntentionAction.canModify(myVariable) &&
|
||||
!myVariable.hasInitializer() &&
|
||||
!(myVariable instanceof PsiParameter);
|
||||
public boolean isAvailable(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiVariable variable = ObjectUtils.tryCast(startElement, PsiVariable.class);
|
||||
return variable != null && variable.isValid() &&
|
||||
BaseIntentionAction.canModify(variable) &&
|
||||
!variable.hasInitializer() &&
|
||||
!(variable instanceof PsiParameter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElement getElementToMakeWritable(@NotNull PsiFile file) {
|
||||
return myVariable;
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
final LookupElement[] suggestedInitializers = suggestInitializer(myVariable);
|
||||
public void invoke(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiVariable variable = ObjectUtils.tryCast(startElement, PsiVariable.class);
|
||||
if (variable == null) return;
|
||||
final LookupElement[] suggestedInitializers = suggestInitializer(variable);
|
||||
LOG.assertTrue(suggestedInitializers.length > 0);
|
||||
LOG.assertTrue(suggestedInitializers[0] instanceof ExpressionLookupItem);
|
||||
final PsiExpression initializer = (PsiExpression)suggestedInitializers[0].getObject();
|
||||
myVariable.setInitializer(initializer);
|
||||
variable.setInitializer(initializer);
|
||||
Document document = Objects.requireNonNull(PsiDocumentManager.getInstance(project).getDocument(file));
|
||||
PsiDocumentManager.getInstance(initializer.getProject()).doPostponedOperationsAndUnblockDocument(document);
|
||||
runAssignmentTemplate(Collections.singletonList(myVariable.getInitializer()), suggestedInitializers, editor);
|
||||
runAssignmentTemplate(Collections.singletonList(variable.getInitializer()), suggestedInitializers, editor);
|
||||
}
|
||||
|
||||
static void runAssignmentTemplate(@NotNull final List<? extends PsiExpression> initializers,
|
||||
@@ -81,6 +93,7 @@ public class AddVariableInitializerFix implements IntentionAction {
|
||||
LOG.assertTrue(!initializers.isEmpty());
|
||||
final PsiExpression initializer = ObjectUtils.notNull(ContainerUtil.getFirstItem(initializers));
|
||||
PsiElement context = initializers.size() == 1 ? initializer : PsiTreeUtil.findCommonParent(initializers);
|
||||
if (context == null) return;
|
||||
final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(context);
|
||||
for (PsiExpression e : initializers) {
|
||||
builder.replaceElement(e, new ConstantNode(new PsiElementResult(suggestedInitializers[0].getPsiElement())).withLookupItems(suggestedInitializers));
|
||||
|
||||
+5
-7
@@ -219,7 +219,7 @@ public class CreateConstructorParameterFromFieldFix implements IntentionAction {
|
||||
};
|
||||
}
|
||||
|
||||
private static List<PsiMethod> filterConstructorsIfFieldAlreadyAssigned(PsiMethod[] constructors, PsiField field) {
|
||||
static List<PsiMethod> filterConstructorsIfFieldAlreadyAssigned(PsiMethod[] constructors, PsiField field) {
|
||||
final List<PsiMethod> result = new ArrayList<>(Arrays.asList(constructors));
|
||||
for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(constructors))) {
|
||||
final PsiElement element = reference.getElement();
|
||||
@@ -296,10 +296,7 @@ public class CreateConstructorParameterFromFieldFix implements IntentionAction {
|
||||
SuggestedNameInfo nameInfo = styleManager.suggestVariableName(VariableKind.PARAMETER, name, null, variable.getType());
|
||||
String newName = nameInfo.names[0];
|
||||
int n = 1;
|
||||
while (true) {
|
||||
if (isUnique(parameters, newName, usedNames)) {
|
||||
break;
|
||||
}
|
||||
while (!isUnique(parameters, newName, usedNames)) {
|
||||
newName = n < nameInfo.names.length &&
|
||||
!JavaCodeStyleSettings.getInstance(variable.getContainingFile()).PREFER_LONGER_NAMES
|
||||
? nameInfo.names[n++] : nameInfo.names[0] + n++;
|
||||
@@ -327,8 +324,9 @@ public class CreateConstructorParameterFromFieldFix implements IntentionAction {
|
||||
if (JavaHighlightUtil.getChainedConstructors(constructor).isEmpty()) {
|
||||
final SmartPointerManager manager = SmartPointerManager.getInstance(project);
|
||||
boolean created = false;
|
||||
for (PsiField field : fields.keySet()) {
|
||||
final String defaultParamName = fields.get(field);
|
||||
for (Map.Entry<PsiField, String> entry : fields.entrySet()) {
|
||||
PsiField field = entry.getKey();
|
||||
final String defaultParamName = entry.getValue();
|
||||
PsiParameter parameter = findParamByName(defaultParamName, field.getType(), newParameters, parameterInfos);
|
||||
if (parameter == null) {
|
||||
continue;
|
||||
|
||||
+31
-34
@@ -1,12 +1,11 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.generation.PsiMethodMember;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -15,23 +14,17 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class InitializeFinalFieldInConstructorFix implements IntentionAction {
|
||||
public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntentionActionOnPsiElement {
|
||||
private static final Logger LOG = Logger.getInstance(InitializeFinalFieldInConstructorFix.class);
|
||||
private final PsiField myField;
|
||||
|
||||
public InitializeFinalFieldInConstructorFix(@NotNull PsiField field) {
|
||||
myField = field;
|
||||
super(field);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -47,35 +40,45 @@ public class InitializeFinalFieldInConstructorFix implements IntentionAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
if (!myField.isValid() || myField.hasModifierProperty(PsiModifier.STATIC) || myField.hasInitializer()) {
|
||||
public boolean isAvailable(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiField field = ObjectUtils.tryCast(startElement, PsiField.class);
|
||||
if (field == null) return false;
|
||||
if (!field.isValid() || field.hasModifierProperty(PsiModifier.STATIC) || field.hasInitializer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PsiClass containingClass = myField.getContainingClass();
|
||||
final PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass == null || containingClass.getName() == null){
|
||||
return false;
|
||||
}
|
||||
|
||||
final PsiManager manager = myField.getManager();
|
||||
return manager != null && BaseIntentionAction.canModify(myField);
|
||||
final PsiManager manager = field.getManager();
|
||||
return manager != null && BaseIntentionAction.canModify(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
|
||||
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
|
||||
|
||||
final PsiClass myClass = myField.getContainingClass();
|
||||
if (myClass == null) {
|
||||
return;
|
||||
}
|
||||
public void invoke(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiField field = ObjectUtils.tryCast(startElement, PsiField.class);
|
||||
if (field == null) return;
|
||||
final PsiClass myClass = field.getContainingClass();
|
||||
if (myClass == null) return;
|
||||
if (myClass.getConstructors().length == 0) {
|
||||
createDefaultConstructor(myClass, project, editor, file);
|
||||
}
|
||||
|
||||
final List<PsiMethod> constructors = choose(filterIfFieldAlreadyAssigned(myField, myClass.getConstructors()), project);
|
||||
PsiMethod[] ctors = CreateConstructorParameterFromFieldFix.filterConstructorsIfFieldAlreadyAssigned(myClass.getConstructors(), field)
|
||||
.toArray(PsiMethod.EMPTY_ARRAY);
|
||||
final List<PsiMethod> constructors = choose(ctors, project);
|
||||
|
||||
ApplicationManager.getApplication().runWriteAction(() -> addFieldInitialization(constructors, myField, project, editor));
|
||||
ApplicationManager.getApplication().runWriteAction(() -> addFieldInitialization(constructors, field, project, editor));
|
||||
}
|
||||
|
||||
private static void addFieldInitialization(@NotNull List<? extends PsiMethod> constructors,
|
||||
@@ -180,16 +183,10 @@ public class InitializeFinalFieldInConstructorFix implements IntentionAction {
|
||||
ApplicationManager.getApplication().runWriteAction(() -> defaultConstructorFix.invoke(project, editor, file));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiMethod[] filterIfFieldAlreadyAssigned(@NotNull PsiField field, @NotNull PsiMethod[] ctors) {
|
||||
final List<PsiMethod> result = new ArrayList<>(Arrays.asList(ctors));
|
||||
for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(ctors))) {
|
||||
final PsiElement element = reference.getElement();
|
||||
if (element instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)element)) {
|
||||
result.remove(PsiTreeUtil.getParentOfType(element, PsiMethod.class));
|
||||
}
|
||||
}
|
||||
return result.toArray(PsiMethod.EMPTY_ARRAY);
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
|
||||
return currentFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.nullable;
|
||||
|
||||
import com.intellij.codeInsight.Nullability;
|
||||
import com.intellij.codeInsight.NullabilityAnnotationInfo;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
||||
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddVariableInitializerFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.InitializeFinalFieldInConstructorFix;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
||||
import com.intellij.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private static final String IGNORE_IMPLICITLY_WRITTEN_FIELDS_NAME = "IGNORE_IMPLICITLY_WRITTEN_FIELDS";
|
||||
public boolean IGNORE_IMPLICITLY_WRITTEN_FIELDS = true;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JComponent createOptionsPanel() {
|
||||
return new SingleCheckboxOptionsPanel(InspectionsBundle.message("inspection.notnull.field.not.initialized.option"),
|
||||
this, IGNORE_IMPLICITLY_WRITTEN_FIELDS_NAME);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitField(PsiField field) {
|
||||
NullableNotNullManager manager = NullableNotNullManager.getInstance(holder.getProject());
|
||||
NullabilityAnnotationInfo info = manager.findEffectiveNullabilityInfo(field);
|
||||
if (info == null ||
|
||||
info.getNullability() != Nullability.NOT_NULL ||
|
||||
HighlightControlFlowUtil.isFieldInitializedAfterObjectConstruction(field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean implicitWrite = UnusedSymbolUtil.isImplicitWrite(field);
|
||||
if (IGNORE_IMPLICITLY_WRITTEN_FIELDS && implicitWrite) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean byDefault = info.isContainer();
|
||||
PsiAnnotation annotation = info.getAnnotation();
|
||||
PsiJavaCodeReferenceElement name = annotation.getNameReferenceElement();
|
||||
boolean ownAnnotation = annotation.isPhysical() && !byDefault;
|
||||
PsiElement anchor = ownAnnotation ? annotation : field.getNameIdentifier();
|
||||
String message = (byDefault && name != null ? "@" + name.getReferenceName() : "Not-null") + " fields must be initialized";
|
||||
|
||||
List<LocalQuickFix> fixes = new ArrayList<>();
|
||||
if (implicitWrite && isOnTheFly) {
|
||||
fixes.add(new SetInspectionOptionFix(NotNullFieldNotInitializedInspection.this,
|
||||
IGNORE_IMPLICITLY_WRITTEN_FIELDS_NAME,
|
||||
InspectionsBundle.message("inspection.notnull.field.not.initialized.option"), true));
|
||||
}
|
||||
if (ownAnnotation) {
|
||||
fixes.add(new DeleteElementFix(annotation, "Remove not-null annotation"));
|
||||
}
|
||||
if (isOnTheFly) {
|
||||
fixes.add(new InitializeFinalFieldInConstructorFix(field));
|
||||
fixes.add(new AddVariableInitializerFix(field));
|
||||
}
|
||||
|
||||
holder.registerProblem(anchor, message, fixes.toArray(LocalQuickFix.EMPTY_ARRAY));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
|
||||
private JButton myConfigureAnnotationsButton;
|
||||
private JCheckBox myIgnoreExternalSuperNotNull;
|
||||
private JCheckBox myNNParameterOverridesNA;
|
||||
private JCheckBox myRequireNNFieldsInitialized;
|
||||
private JBCheckBox myReportNullLiteralsPassedNotNullParameter;
|
||||
|
||||
private OptionsPanel() {
|
||||
@@ -78,7 +77,6 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
|
||||
myNNParameterOverridesNA.addActionListener(actionListener);
|
||||
myReportNotAnnotatedGetter.addActionListener(actionListener);
|
||||
myIgnoreExternalSuperNotNull.addActionListener(actionListener);
|
||||
myRequireNNFieldsInitialized.addActionListener(actionListener);
|
||||
myReportNullLiteralsPassedNotNullParameter.addActionListener(actionListener);
|
||||
myConfigureAnnotationsButton.addActionListener(NullableNotNullDialog.createActionListener(this));
|
||||
reset();
|
||||
@@ -90,7 +88,6 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
|
||||
myReportNotAnnotatedGetter.setSelected(REPORT_NOT_ANNOTATED_GETTER);
|
||||
myIgnoreExternalSuperNotNull.setSelected(IGNORE_EXTERNAL_SUPER_NOTNULL);
|
||||
myNNParameterOverridesNA.setSelected(REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED);
|
||||
myRequireNNFieldsInitialized.setSelected(REQUIRE_NOTNULL_FIELDS_INITIALIZED);
|
||||
myReportNullLiteralsPassedNotNullParameter.setSelected(REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER);
|
||||
|
||||
myIgnoreExternalSuperNotNull.setEnabled(myNAMethodOverridesNN.isSelected());
|
||||
@@ -102,7 +99,6 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
|
||||
REPORT_NOT_ANNOTATED_GETTER = myReportNotAnnotatedGetter.isSelected();
|
||||
IGNORE_EXTERNAL_SUPER_NOTNULL = myIgnoreExternalSuperNotNull.isSelected();
|
||||
REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = myNNParameterOverridesNA.isSelected();
|
||||
REQUIRE_NOTNULL_FIELDS_INITIALIZED = myRequireNNFieldsInitialized.isSelected();
|
||||
REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = myReportNullLiteralsPassedNotNullParameter.isSelected();
|
||||
REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS = REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.codeInspection.nullable.NullableStuffInspection.OptionsPanel">
|
||||
<grid id="cc1c9" binding="myPanel" layout-manager="GridLayoutManager" row-count="9" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="cc1c9" binding="myPanel" layout-manager="GridLayoutManager" row-count="8" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="69" y="57" width="634" height="277"/>
|
||||
@@ -10,7 +10,7 @@
|
||||
<children>
|
||||
<vspacer id="c3eef">
|
||||
<constraints>
|
||||
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="2f304" class="javax.swing.JCheckBox" binding="myReportNotAnnotatedGetter">
|
||||
@@ -39,7 +39,7 @@
|
||||
</component>
|
||||
<component id="ef852" class="javax.swing.JButton" binding="myConfigureAnnotationsButton" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/InspectionsBundle" key="configure.annotations.option"/>
|
||||
@@ -61,17 +61,9 @@
|
||||
<text value="Report @NotNull &parameters overriding non-annotated"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f6e46" class="javax.swing.JCheckBox" binding="myRequireNNFieldsInitialized">
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Require @NotNull fields to be initialized explicitly"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="9b307" class="com.intellij.ui.components.JBCheckBox" binding="myReportNullLiteralsPassedNotNullParameter" default-binding="true">
|
||||
<constraints>
|
||||
<grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Report @NotNull parameters with null-literal argument usages"/>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports fields annotated as not-null which are not initialized in constructor. Example:
|
||||
<pre>public class MyClass {
|
||||
private @NotNull String value;
|
||||
|
||||
public void setValue(@NotNull String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public @NotNull String getValue() {
|
||||
return value;
|
||||
}
|
||||
}</pre>
|
||||
<p>
|
||||
Such fields may violate the not-null constraint. E.g. in the example above while <code>setValue</code> parameter is annotated as not-null
|
||||
<code>getValue</code> may return null if setter was not called.
|
||||
</p>
|
||||
<!-- tooltip end -->
|
||||
<p>Use the checkbox below to control whether warning should be issued if field could be initialized implicitly
|
||||
(e.g. via dependency injection).</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,6 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
class Test {
|
||||
<warning descr="Not-null fields must be initialized">@NotNull</warning> String explicit;
|
||||
@NotNull String implicit;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Initialize variable 'x'" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
@NotNull String x = null;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Initialize in constructor" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
@NotNull String x;
|
||||
|
||||
X() {
|
||||
x = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Remove not-null annotation" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
String x;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Initialize variable 'x'" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
@<caret>NotNull String x;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// "Initialize in constructor" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
@<caret>NotNull String x;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Remove not-null annotation" "true"
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class X {
|
||||
@<caret>NotNull String x;
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
class Test {
|
||||
@NotNull Object member;
|
||||
|
||||
private void accessMember() {
|
||||
member = new Object();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.nullable.NotNullFieldNotInitializedInspection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class NotNullFieldNotInitializedFixesTest extends LightQuickFixParameterizedTestCase {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LocalInspectionTool[] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new NotNullFieldNotInitializedInspection()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/inspection/notNullField/quickFix";
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
|
||||
import com.intellij.codeInspection.nullable.NotNullFieldNotInitializedInspection;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class NotNullFieldNotInitializedInspectionTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
public void testNotNullFieldNotInitialized() { doTest(); }
|
||||
public void testNotNullFieldInitializedInLambda() { doTest(); }
|
||||
public void testNotNullFieldNotInitializedInOneConstructor() { doTest(); }
|
||||
public void testTypeUseNotNullField() {
|
||||
DataFlowInspection8Test.setupTypeUseAnnotations("typeUse", myFixture);
|
||||
doTest();
|
||||
}
|
||||
public void testNotNullByDefaultFieldNotInitialized() {
|
||||
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
|
||||
doTest();
|
||||
}
|
||||
public void testImplicit() {
|
||||
PlatformTestUtil.registerExtension(Extensions.getRootArea(), ImplicitUsageProvider.EP_NAME, new ImplicitUsageProvider() {
|
||||
@Override
|
||||
public boolean isImplicitUsage(@NotNull PsiElement element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImplicitRead(@NotNull PsiElement element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImplicitWrite(@NotNull PsiElement element) {
|
||||
return element instanceof PsiField && "implicit".equals(((PsiField)element).getName());
|
||||
}
|
||||
}, getTestRootDisposable());
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTest() {
|
||||
myFixture.enableInspections(new NotNullFieldNotInitializedInspection());
|
||||
myFixture.testHighlighting(true, false, true, getTestName(false) + ".java");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_8_ANNOTATED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return JavaTestUtil.getJavaTestDataPath() + "/inspection/notNullField/";
|
||||
}
|
||||
|
||||
}
|
||||
-18
@@ -73,19 +73,6 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
|
||||
public void testNotNullFieldNullableParam() { doTest(); }
|
||||
public void testNotNullCustomException() { doTest(); }
|
||||
|
||||
public void testNotNullFieldNotInitialized() { doTest(); }
|
||||
public void testNotNullFieldInitializedInLambda() { doTest(); }
|
||||
public void testNotNullFieldNotInitializedInOneConstructor() { doTest(); }
|
||||
public void testNotNullFieldNotInitializedSetting() {
|
||||
myInspection.REQUIRE_NOTNULL_FIELDS_INITIALIZED = false;
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNotNullByDefaultFieldNotInitialized() {
|
||||
DataFlowInspectionTest.addJavaxNullabilityAnnotations(myFixture);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testNotNullAnnotationChecksInChildClassMethods() { doTest(); }
|
||||
|
||||
public void testGetterSetterProblems() { doTest(); }
|
||||
@@ -275,11 +262,6 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTypeUseNotNullField() {
|
||||
DataFlowInspection8Test.setupTypeUseAnnotations("typeUse", myFixture);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTypeUseNotNullOverriding() {
|
||||
myInspection.REPORT_NOTNULL_PARAMETERS_OVERRIDES_NOT_ANNOTATED = true;
|
||||
DataFlowInspection8Test.setupTypeUseAnnotations("typeUse", myFixture);
|
||||
|
||||
@@ -155,6 +155,9 @@ inspection.return.separated.from.computation.descriptor=Return separated from co
|
||||
inspection.return.separated.from.computation.quickfix=Move ''return'' closer to computation of the value of ''{0}''
|
||||
inspection.return.separated.from.computation.family.quickfix=Move 'return' closer to computation of the result
|
||||
|
||||
inspection.notnull.field.not.initialized.display.name=@NotNull field is not initialized
|
||||
inspection.notnull.field.not.initialized.option=Ignore fields which could be initialized implicitly
|
||||
|
||||
inspection.nullable.problems.display.name=@NotNull/@Nullable problems
|
||||
#check box options
|
||||
inspection.nullable.problems.method.overrides.notnull.option=<html>Report @NotNull ¶meters overriding @Nullable and <br>@Nullable methods overriding @NotNull</html>
|
||||
@@ -566,6 +569,7 @@ edit.options.of.reporter.inspection.text=Edit inspection profile setting
|
||||
group.names.abstraction.issues=Abstraction issues
|
||||
group.names.assignment.issues=Assignment issues
|
||||
group.names.probable.bugs=Probable bugs
|
||||
group.names.nullability.problems=Nullability problems
|
||||
group.names.bitwise.operation.issues=Bitwise operation issues
|
||||
group.names.class.structure=Class structure
|
||||
group.names.class.metrics=Class metrics
|
||||
|
||||
@@ -345,8 +345,8 @@
|
||||
key="use.0index.in.jdbc.resultset.display.name" groupBundle="messages.InspectionsBundle"
|
||||
groupKey="group.names.probable.bugs" enabledByDefault="false" level="WARNING"
|
||||
implementationClass="com.siyeh.ig.bugs.ResultSetIndexZeroInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" suppressId="ReturnOfNull" shortName="ReturnNull" bundle="com.siyeh.InspectionGadgetsBundle"
|
||||
key="return.of.null.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.probable.bugs"
|
||||
<localInspection groupPath="Java,Probable bugs" language="JAVA" suppressId="ReturnOfNull" shortName="ReturnNull" bundle="com.siyeh.InspectionGadgetsBundle"
|
||||
key="return.of.null.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.nullability.problems"
|
||||
enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.bugs.ReturnNullInspection"/>
|
||||
<localInspection groupPath="Java" language="JAVA" suppressId="StaticMethodReferencedViaSubclass" shortName="StaticCallOnSubclass"
|
||||
bundle="com.siyeh.InspectionGadgetsBundle" key="static.method.via.subclass.display.name"
|
||||
|
||||
Reference in New Issue
Block a user