diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java index 81fec1e746dd..8168b4fae04e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java @@ -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> 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, diff --git a/java/java-analysis-impl/src/messages/QuickFixBundle.properties b/java/java-analysis-impl/src/messages/QuickFixBundle.properties index 7270a17224ff..134044b4f00b 100644 --- a/java/java-analysis-impl/src/messages/QuickFixBundle.properties +++ b/java/java-analysis-impl/src/messages/QuickFixBundle.properties @@ -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 diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 5e733dd1e2f8..3743a5363b57 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -1506,9 +1506,12 @@ - + diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddVariableInitializerFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddVariableInitializerFix.java index f966b80d4cf2..19b7d59121d3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddVariableInitializerFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddVariableInitializerFix.java @@ -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 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)); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorParameterFromFieldFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorParameterFromFieldFix.java index 49cbc444e08a..1333b90ee8f4 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorParameterFromFieldFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/CreateConstructorParameterFromFieldFix.java @@ -219,7 +219,7 @@ public class CreateConstructorParameterFromFieldFix implements IntentionAction { }; } - private static List filterConstructorsIfFieldAlreadyAssigned(PsiMethod[] constructors, PsiField field) { + static List filterConstructorsIfFieldAlreadyAssigned(PsiMethod[] constructors, PsiField field) { final List 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 entry : fields.entrySet()) { + PsiField field = entry.getKey(); + final String defaultParamName = entry.getValue(); PsiParameter parameter = findParamByName(defaultParamName, field.getType(), newParameters, parameterInfos); if (parameter == null) { continue; diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java index 861df614d582..3b7ce9a1bdd0 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java @@ -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 constructors = choose(filterIfFieldAlreadyAssigned(myField, myClass.getConstructors()), project); + PsiMethod[] ctors = CreateConstructorParameterFromFieldFix.filterConstructorsIfFieldAlreadyAssigned(myClass.getConstructors(), field) + .toArray(PsiMethod.EMPTY_ARRAY); + final List 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 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 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 diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NotNullFieldNotInitializedInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NotNullFieldNotInitializedInspection.java new file mode 100644 index 000000000000..1ea83c5a0239 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NotNullFieldNotInitializedInspection.java @@ -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 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)); + } + }; + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java index 02e1892784d6..021803506890 100644 --- a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java @@ -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; diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/OptionsPanel.form b/java/java-impl/src/com/intellij/codeInspection/nullable/OptionsPanel.form index ff37811fc3cf..e1f5448f1202 100644 --- a/java/java-impl/src/com/intellij/codeInspection/nullable/OptionsPanel.form +++ b/java/java-impl/src/com/intellij/codeInspection/nullable/OptionsPanel.form @@ -1,6 +1,6 @@
- + @@ -10,7 +10,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -61,17 +61,9 @@ - - - - - - - - - + diff --git a/java/java-impl/src/inspectionDescriptions/NotNullFieldNotInitialized.html b/java/java-impl/src/inspectionDescriptions/NotNullFieldNotInitialized.html new file mode 100644 index 000000000000..4888bff59887 --- /dev/null +++ b/java/java-impl/src/inspectionDescriptions/NotNullFieldNotInitialized.html @@ -0,0 +1,23 @@ + + +Reports fields annotated as not-null which are not initialized in constructor. Example: +
public class MyClass {
+  private @NotNull String value;
+
+  public void setValue(@NotNull String value) {
+    this.value = value;
+  }
+
+  public @NotNull String getValue() {
+    return value;
+  }
+}
+

+ Such fields may violate the not-null constraint. E.g. in the example above while setValue parameter is annotated as not-null + getValue may return null if setter was not called. +

+ +

Use the checkbox below to control whether warning should be issued if field could be initialized implicitly + (e.g. via dependency injection).

+ + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/Implicit.java b/java/java-tests/testData/inspection/notNullField/Implicit.java new file mode 100644 index 000000000000..ac5555ffb2d1 --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/Implicit.java @@ -0,0 +1,6 @@ +import org.jetbrains.annotations.*; + +class Test { + @NotNull String explicit; + @NotNull String implicit; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultFieldNotInitialized.java b/java/java-tests/testData/inspection/notNullField/NotNullByDefaultFieldNotInitialized.java similarity index 100% rename from java/java-tests/testData/inspection/nullableProblems/NotNullByDefaultFieldNotInitialized.java rename to java/java-tests/testData/inspection/notNullField/NotNullByDefaultFieldNotInitialized.java diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullFieldInitializedInLambda.java b/java/java-tests/testData/inspection/notNullField/NotNullFieldInitializedInLambda.java similarity index 100% rename from java/java-tests/testData/inspection/nullableProblems/NotNullFieldInitializedInLambda.java rename to java/java-tests/testData/inspection/notNullField/NotNullFieldInitializedInLambda.java diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitialized.java b/java/java-tests/testData/inspection/notNullField/NotNullFieldNotInitialized.java similarity index 100% rename from java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitialized.java rename to java/java-tests/testData/inspection/notNullField/NotNullFieldNotInitialized.java diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitializedInOneConstructor.java b/java/java-tests/testData/inspection/notNullField/NotNullFieldNotInitializedInOneConstructor.java similarity index 100% rename from java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitializedInOneConstructor.java rename to java/java-tests/testData/inspection/notNullField/NotNullFieldNotInitializedInOneConstructor.java diff --git a/java/java-tests/testData/inspection/nullableProblems/TypeUseNotNullField.java b/java/java-tests/testData/inspection/notNullField/TypeUseNotNullField.java similarity index 100% rename from java/java-tests/testData/inspection/nullableProblems/TypeUseNotNullField.java rename to java/java-tests/testData/inspection/notNullField/TypeUseNotNullField.java diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeField.java b/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeField.java new file mode 100644 index 000000000000..234d0993614e --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeField.java @@ -0,0 +1,6 @@ +// "Initialize variable 'x'" "true" +import org.jetbrains.annotations.NotNull; + +class X { + @NotNull String x = null; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeInConstructor.java b/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeInConstructor.java new file mode 100644 index 000000000000..a1867fc7550f --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/afterInitializeInConstructor.java @@ -0,0 +1,10 @@ +// "Initialize in constructor" "true" +import org.jetbrains.annotations.NotNull; + +class X { + @NotNull String x; + + X() { + x = null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/afterRemoveAnnotation.java b/java/java-tests/testData/inspection/notNullField/quickFix/afterRemoveAnnotation.java new file mode 100644 index 000000000000..b0f8b40ecf69 --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/afterRemoveAnnotation.java @@ -0,0 +1,6 @@ +// "Remove not-null annotation" "true" +import org.jetbrains.annotations.NotNull; + +class X { + String x; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeField.java b/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeField.java new file mode 100644 index 000000000000..cd9dcec44ad6 --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeField.java @@ -0,0 +1,6 @@ +// "Initialize variable 'x'" "true" +import org.jetbrains.annotations.NotNull; + +class X { + @NotNull String x; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeInConstructor.java b/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeInConstructor.java new file mode 100644 index 000000000000..f2e748a5f63b --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/beforeInitializeInConstructor.java @@ -0,0 +1,6 @@ +// "Initialize in constructor" "true" +import org.jetbrains.annotations.NotNull; + +class X { + @NotNull String x; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/notNullField/quickFix/beforeRemoveAnnotation.java b/java/java-tests/testData/inspection/notNullField/quickFix/beforeRemoveAnnotation.java new file mode 100644 index 000000000000..6f32259c3f9e --- /dev/null +++ b/java/java-tests/testData/inspection/notNullField/quickFix/beforeRemoveAnnotation.java @@ -0,0 +1,6 @@ +// "Remove not-null annotation" "true" +import org.jetbrains.annotations.NotNull; + +class X { + @NotNull String x; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitializedSetting.java b/java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitializedSetting.java deleted file mode 100644 index 5513b01408d9..000000000000 --- a/java/java-tests/testData/inspection/nullableProblems/NotNullFieldNotInitializedSetting.java +++ /dev/null @@ -1,9 +0,0 @@ -import org.jetbrains.annotations.*; - -class Test { - @NotNull Object member; - - private void accessMember() { - member = new Object(); - } -} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedFixesTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedFixesTest.java new file mode 100644 index 000000000000..962268217ecd --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedFixesTest.java @@ -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"; + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedInspectionTest.java new file mode 100644 index 000000000000..b201e4178b93 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NotNullFieldNotInitializedInspectionTest.java @@ -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/"; + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java index d5c3cd9ad7cc..b4313430825c 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java @@ -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); diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 63b1d92b0d20..0307bc838710 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -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=Report @NotNull ¶meters overriding @Nullable and
@Nullable methods overriding @NotNull @@ -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 diff --git a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml index 697a1c33a3b8..156cf001491c 100644 --- a/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/src/META-INF/InspectionGadgets.xml @@ -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"/> -