From 4882d4a58993dff21f82078701cbbdd092b95a83 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 27 Jul 2017 18:46:51 +0700 Subject: [PATCH] IDEA-176650 Provide a quick fix of "can produce NPE" that wraps qualifier with Obejcts.requireNonNull --- .../dataFlow/DataFlowInspectionBase.java | 9 +-- .../fix/SurroundWithRequireNonNullFix.java | 62 +++++++++++++++++++ .../afterArrayAccess.java | 10 +++ .../afterFieldDereference.java | 14 +++++ .../afterMethodArgument.java | 12 ++++ .../afterMethodArgument2.java | 12 ++++ .../afterMethodCall.java | 11 ++++ .../beforeArrayAccess.java | 8 +++ .../beforeFieldDereference.java | 12 ++++ .../beforeMethodArgument.java | 10 +++ .../beforeMethodArgument2.java | 11 ++++ .../beforeMethodCall.java | 10 +++ .../SurroundWithRequireNonNullFixTest.java | 41 ++++++++++++ .../src/messages/InspectionsBundle.properties | 1 + 14 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index f9850537dc75..d16599653099 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -24,10 +24,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpressionFi import com.intellij.codeInsight.intention.impl.AddNotNullAnnotationFix; import com.intellij.codeInsight.intention.impl.AddNullableAnnotationFix; import com.intellij.codeInspection.*; -import com.intellij.codeInspection.dataFlow.fix.RedundantInstanceofFix; -import com.intellij.codeInspection.dataFlow.fix.ReplaceWithConstantValueFix; -import com.intellij.codeInspection.dataFlow.fix.ReplaceWithObjectsEqualsFix; -import com.intellij.codeInspection.dataFlow.fix.SimplifyToAssignmentFix; +import com.intellij.codeInspection.dataFlow.fix.*; import com.intellij.codeInspection.dataFlow.instructions.*; import com.intellij.codeInspection.dataFlow.value.DfaConstValue; import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue; @@ -286,6 +283,10 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } + if (PsiUtil.isLanguageLevel7OrHigher(qualifier)) { + fixes.add(new SurroundWithRequireNonNullFix(qualifier)); + } + ContainerUtil.addIfNotNull(fixes, DfaOptionalSupport.registerReplaceOptionalOfWithOfNullableFix(qualifier)); } catch (IncorrectOperationException e) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java new file mode 100644 index 000000000000..460bf941806e --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/fix/SurroundWithRequireNonNullFix.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.dataFlow.fix; + +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiExpression; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class SurroundWithRequireNonNullFix implements LocalQuickFix { + private final String myText; + private final SmartPsiElementPointer myQualifierPointer; + + public SurroundWithRequireNonNullFix(@NotNull PsiExpression expressionToSurround) { + myText = expressionToSurround.getText(); + myQualifierPointer = + SmartPointerManager.getInstance(expressionToSurround.getProject()).createSmartPsiElementPointer(expressionToSurround); + } + + @Nls + @NotNull + @Override + public String getName() { + return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", myText); + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionsBundle.message("inspection.surround.requirenonnull.quickfix", ""); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiExpression qualifier = myQualifierPointer.getElement(); + if (qualifier == null) return; + PsiExpression replacement = JavaPsiFacade.getElementFactory(project) + .createExpressionFromText("java.util.Objects.requireNonNull(" + qualifier.getText() + ")", qualifier); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(qualifier.replace(replacement)); + } +} diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java new file mode 100644 index 000000000000..833a69757ae7 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterArrayAccess.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +import java.util.Objects; + +class MyClass { + void test() { + int[] arr = Math.random() > 0.5 ? null : new int[10]; + System.out.println(Objects.requireNonNull(arr)[1]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java new file mode 100644 index 000000000000..6f981f5475fc --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterFieldDereference.java @@ -0,0 +1,14 @@ +// "Replace with 'Objects.requireNonNull(getObject())'" "true" + +import java.util.Objects; + +class MyClass { + int a; + + static MyClass getObject() { + return Math.random() > 0.5 ? new MyClass() : null; + } + void test() { + Objects.requireNonNull(getObject()).a = 5; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java new file mode 100644 index 000000000000..0b8833741ab4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +import java.util.Objects; + +class MyClass { + void foo(String[] arr) {} + + void test() { + String[] arr = Math.random() > 0.5 ? null : new String[10]; + foo(Objects.requireNonNull(arr)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java new file mode 100644 index 000000000000..a460146b68ec --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodArgument2.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")'" "true" + +import java.util.List; +import java.util.Objects; + +class MyClass { + void foo(String str) {} + + void test() { + foo(Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java new file mode 100644 index 000000000000..cd097f2f859b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/afterMethodCall.java @@ -0,0 +1,11 @@ +// "Replace with 'Objects.requireNonNull(s1)'" "true" +import java.util.List; +import java.util.Objects; + +class MyClass { + void test(List list) { + list.stream().map(s -> s.isEmpty() ? null : s) + .map(s1 -> Objects.requireNonNull(s1).trim()) + .forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java new file mode 100644 index 000000000000..97dc30bbaebd --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeArrayAccess.java @@ -0,0 +1,8 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +class MyClass { + void test() { + int[] arr = Math.random() > 0.5 ? null : new int[10]; + System.out.println(arr[1]); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java new file mode 100644 index 000000000000..7ae951894d71 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeFieldDereference.java @@ -0,0 +1,12 @@ +// "Replace with 'Objects.requireNonNull(getObject())'" "true" + +class MyClass { + int a; + + static MyClass getObject() { + return Math.random() > 0.5 ? new MyClass() : null; + } + void test() { + getObject().a = 5; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java new file mode 100644 index 000000000000..4b17793dfc51 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(arr)'" "true" + +class MyClass { + void foo(String[] arr) {} + + void test() { + String[] arr = Math.random() > 0.5 ? null : new String[10]; + foo(arr); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java new file mode 100644 index 000000000000..b17be30ea19d --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodArgument2.java @@ -0,0 +1,11 @@ +// "Replace with 'Objects.requireNonNull(Math.random() > 0.5 ? null : "bar")'" "true" + +import java.util.List; + +class MyClass { + void foo(String str) {} + + void test() { + foo(Math.random() > 0.5 ? null : "bar"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java new file mode 100644 index 000000000000..fe0f437804e8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull/beforeMethodCall.java @@ -0,0 +1,10 @@ +// "Replace with 'Objects.requireNonNull(s1)'" "true" +import java.util.List; + +class MyClass { + void test(List list) { + list.stream().map(s -> s.isEmpty() ? null : s) + .map(s1 -> s1.trim()) + .forEach(System.out::println); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java new file mode 100644 index 000000000000..733b7908e318 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/quickFix/SurroundWithRequireNonNullFixTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.java.codeInsight.daemon.quickFix; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.dataFlow.DataFlowInspection; +import org.jetbrains.annotations.NotNull; + +public class SurroundWithRequireNonNullFixTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + DataFlowInspection inspection = new DataFlowInspection(); + inspection.SUGGEST_NULLABLE_ANNOTATIONS = true; + return new LocalInspectionTool[]{inspection}; + } + + public void test() throws Exception { + doAllTests(); + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/surroundWithRequireNonNull"; + } +} \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 1b998af7cfe8..9f4dcb160154 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -326,6 +326,7 @@ inspection.surround.if.quickfix=Surround with ''if ({0} != null)'' inspection.replace.ternary.quickfix=Replace with ''{0} != null ?:'' inspection.surround.if.family=Surround with if inspection.dependency.configure.button.text=Configure dependency rules +inspection.surround.requirenonnull.quickfix=Replace with ''Objects.requireNonNull({0})'' inspection.javadoc.label.text=Additional Javadoc Tags: inspection.javadoc.dialog.title=Edit Additional Javadoc Tags