From 1b7a05e26fbcdd471cfebb1f8e52ee7e847dfcbb Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 14 Apr 2015 17:14:03 +0300 Subject: [PATCH] IDEA-138553 Inspection for usages of Optinoal.ofNullable() for values known to be null or non-null --- .../dataFlow/DataFlowInspectionBase.java | 84 +++-------- .../dataFlow/DfaOptionalSupport.java | 139 ++++++++++++++++++ .../dataFlow/NullabilityProblem.java | 2 + .../dataFlow/StandardInstructionVisitor.java | 21 ++- .../afterGuavaNotNullLiteral.java | 6 + .../afterGuavaNullLiteral.java | 6 + .../afterNotNullLiteral.java | 6 + .../afterNotNullVariable.java | 8 + .../afterNullLiteral.java | 7 + .../afterNullVariable.java | 7 + .../beforeGuavaNotNullLiteral.java | 6 + .../beforeGuavaNullLiteral.java | 6 + .../beforeNotNullLiteral.java | 6 + .../beforeNotNullVariable.java | 8 + .../beforeNullLiteral.java | 7 + .../beforeNullVariable.java | 7 + .../replaceFromOfNullable/beforeUnknown.java | 6 + .../ReplaceFromOfNullableFixTest.groovy | 86 +++++++++++ .../DataFlowInspectionTestSuite.java | 12 ++ 19 files changed, 364 insertions(+), 66 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNotNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullVariable.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullVariable.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNotNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullVariable.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullLiteral.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullVariable.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeUnknown.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ReplaceFromOfNullableFixTest.groovy 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 68a74f887f3f..8c3804d8a500 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 @@ -53,7 +53,6 @@ import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jdom.Element; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -211,7 +210,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { } } - ContainerUtil.addIfNotNull(fixes, ReplaceOptionalOfWithOfNullableFix.registerReplaceOptionalOfWithOfNullableFix(qualifier)); + ContainerUtil.addIfNotNull(fixes, DfaOptionalSupport.registerReplaceOptionalOfWithOfNullableFix(qualifier)); return fixes.isEmpty() ? null : fixes.toArray(new LocalQuickFix[fixes.size()]); } catch (IncorrectOperationException e) { @@ -272,11 +271,29 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { reportNullableArgumentsPassedToNonAnnotated(visitor, holder, reportedAnchors); } + reportOptionalOfNullableImprovements(holder, visitor, reportedAnchors); + + if (REPORT_CONSTANT_REFERENCE_VALUES) { reportConstantReferenceValues(holder, visitor, reportedAnchors); } } + private static void reportOptionalOfNullableImprovements(ProblemsHolder holder, + DataFlowInstructionVisitor visitor, + HashSet reportedAnchors) { + for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNullToOptional)) { + if (!reportedAnchors.add(expr)) continue; + holder.registerProblem(expr, "Passing null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithEmptyFix(expr)); + } + for (PsiElement expr : visitor.getProblems(NullabilityProblem.passingNotNullToOptional)) { + if (!reportedAnchors.add(expr)) continue; + holder.registerProblem(expr, "Passing a non-null argument to Optional", + DfaOptionalSupport.createReplaceOptionalOfNullableWithOfFix()); + } + } + private static void reportConstantReferenceValues(ProblemsHolder holder, StandardInstructionVisitor visitor, Set reportedAnchors) { for (Pair pair : visitor.getConstantReferenceValues()) { PsiReferenceExpression ref = pair.first; @@ -764,67 +781,4 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool { boolean normalOk; } } - - private static class ReplaceOptionalOfWithOfNullableFix implements LocalQuickFix { - - private static final String GUAVA_OPTIONAL = "com.google.common.base.Optional"; - private final String myTargetMethodName; - - public ReplaceOptionalOfWithOfNullableFix(final String targetMethodName) { - myTargetMethodName = targetMethodName; - } - - private static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(PsiExpression qualifier) { - final PsiElement argList = PsiUtil.skipParenthesizedExprUp(qualifier).getParent(); - if (argList instanceof PsiExpressionList) { - final PsiElement parent = argList.getParent(); - if (parent instanceof PsiMethodCallExpression) { - final PsiMethod method = ((PsiMethodCallExpression)parent).resolveMethod(); - if (method != null) { - final PsiClass containingClass = method.getContainingClass(); - if ("of".equals(method.getName()) && containingClass != null) { - final String qualifiedName = containingClass.getQualifiedName(); - if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalOfWithOfNullableFix("ofNullable"); - } - else if (GUAVA_OPTIONAL.equals(qualifiedName)) { - return new ReplaceOptionalOfWithOfNullableFix("fromNullable"); - } - } - } - } - } - return null; - } - - @Nls - @NotNull - @Override - public String getName() { - return getFamilyName(); - } - - @NotNull - @Override - public String getFamilyName() { - return "Replace with '." + myTargetMethodName + "()'"; - } - - @Override - public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { - final PsiMethodCallExpression - methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); - if (methodCallExpression != null) { - final PsiElement ofNullableExprName = - ((PsiMethodCallExpression)JavaPsiFacade.getElementFactory(project) - .createExpressionFromText("Optional.ofNullable(null)", null)).getMethodExpression(); - final PsiElement referenceNameElement = methodCallExpression.getMethodExpression().getReferenceNameElement(); - if (referenceNameElement != null) { - final PsiElement ofNullableNameElement = ((PsiReferenceExpression)ofNullableExprName).getReferenceNameElement(); - LOG.assertTrue(ofNullableNameElement != null); - referenceNameElement.replace(ofNullableNameElement); - } - } - } - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java new file mode 100644 index 000000000000..12c0d3ca6f3f --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaOptionalSupport.java @@ -0,0 +1,139 @@ +/* + * Copyright 2000-2015 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; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author anet, peter + */ +class DfaOptionalSupport { + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DfaOptionalSupport"); + private static final String GUAVA_OPTIONAL = "com.google.common.base.Optional"; + + @Nullable + static LocalQuickFix registerReplaceOptionalOfWithOfNullableFix(@NotNull PsiExpression qualifier) { + final PsiElement call = findCallExpression(qualifier); + final PsiMethod method = call == null ? null : ((PsiMethodCallExpression)call).resolveMethod(); + final PsiClass containingClass = method == null ? null : method.getContainingClass(); + if (containingClass != null && "of".equals(method.getName())) { + final String qualifiedName = containingClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("ofNullable", false); + } + if (GUAVA_OPTIONAL.equals(qualifiedName)) { + return new ReplaceOptionalCallFix("fromNullable", false); + } + } + return null; + } + + private static PsiMethodCallExpression findCallExpression(@NotNull PsiElement anchor) { + final PsiElement argList = PsiUtil.skipParenthesizedExprUp(anchor).getParent(); + if (argList instanceof PsiExpressionList) { + final PsiElement parent = argList.getParent(); + if (parent instanceof PsiMethodCallExpression) { + return (PsiMethodCallExpression)parent; + } + } + return null; + } + private static boolean isJdkOptional(@NotNull PsiElement anchor) { + final PsiElement parent = findCallExpression(anchor); + PsiMethod method = parent == null ? null : resolveOfNullable(findCallExpression(anchor)); + return method != null && "ofNullable".equals(method.getName()); + } + + @NotNull + static LocalQuickFix createReplaceOptionalOfNullableWithEmptyFix(@NotNull PsiElement anchor) { + return new ReplaceOptionalCallFix(isJdkOptional(anchor) ? "empty" : "absent", true); + } + + @NotNull + static LocalQuickFix createReplaceOptionalOfNullableWithOfFix() { + return new ReplaceOptionalCallFix("of", false); + } + + @Nullable + static PsiMethod resolveOfNullable(PsiCallExpression expression) { + String name = ((PsiMethodCallExpression)expression).getMethodExpression().getReferenceName(); + if ("ofNullable".equals(name) || "fromNullable".equals(name)) { + PsiMethod method = expression.resolveMethod(); + PsiClass psiClass = method == null ? null : method.getContainingClass(); + String qname = psiClass == null ? null : psiClass.getQualifiedName(); + if (CommonClassNames.JAVA_UTIL_OPTIONAL.equals(qname) || GUAVA_OPTIONAL.equals(qname)) { + return method; + } + } + return null; + } + + private static class ReplaceOptionalCallFix implements LocalQuickFix { + private final String myTargetMethodName; + private final boolean myClearArguments; + + public ReplaceOptionalCallFix(final String targetMethodName, boolean clearArguments) { + myTargetMethodName = targetMethodName; + myClearArguments = clearArguments; + } + + @Nls + @NotNull + @Override + public String getName() { + return getFamilyName(); + } + + @NotNull + @Override + public String getFamilyName() { + return "Replace with '." + myTargetMethodName + "()'"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiMethodCallExpression + methodCallExpression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); + if (methodCallExpression != null) { + final PsiElement ofNullableExprName = + ((PsiMethodCallExpression)JavaPsiFacade.getElementFactory(project) + .createExpressionFromText("Optional." + myTargetMethodName + "(null)", null)).getMethodExpression(); + final PsiElement referenceNameElement = methodCallExpression.getMethodExpression().getReferenceNameElement(); + if (referenceNameElement != null) { + final PsiElement ofNullableNameElement = ((PsiReferenceExpression)ofNullableExprName).getReferenceNameElement(); + LOG.assertTrue(ofNullableNameElement != null); + referenceNameElement.replace(ofNullableNameElement); + } + if (myClearArguments) { + PsiExpressionList argList = methodCallExpression.getArgumentList(); + PsiExpression[] args = argList.getExpressions(); + if (args.length > 0) { + argList.deleteChildRange(args[0], args[args.length - 1]); + } + } + } + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java index 8155149ed1ec..2810ffdb2e34 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityProblem.java @@ -11,4 +11,6 @@ public enum NullabilityProblem { nullableReturn, passingNullableToNotNullParameter, passingNullableArgumentToNonAnnotatedParameter, + passingNullToOptional, + passingNotNullToOptional } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index 898c744286bc..66893c0c0c1b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -57,6 +57,15 @@ public class StandardInstructionVisitor extends InstructionVisitor { return callExpression != null ? DfaPsiUtil.getElementNullability(key.getResultType(), callExpression.resolveMethod()) : null; } }; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private final FactoryMap myOptionOfNullable = new FactoryMap() { + @Nullable + @Override + protected Boolean create(MethodCallInstruction key) { + PsiCallExpression expression = key.getCallExpression(); + return expression instanceof PsiMethodCallExpression && DfaOptionalSupport.resolveOfNullable(expression) != null; + } + }; @Override public DfaInstructionState[] visitAssign(AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { @@ -231,6 +240,10 @@ public class StandardInstructionVisitor extends InstructionVisitor { forceNotNull(runner, memState, arg); } } + else if (myOptionOfNullable.get(instruction)) { + checkNotNullable(memState, arg, NullabilityProblem.passingNotNullToOptional, expr); + checkNotNullable(memState, arg, NullabilityProblem.passingNullToOptional, expr); + } else if (requiredNullability == Nullness.UNKNOWN) { checkNotNullable(memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr); } @@ -371,8 +384,14 @@ public class StandardInstructionVisitor extends InstructionVisitor { protected boolean checkNotNullable(DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) { + if (problem == NullabilityProblem.passingNotNullToOptional) { + return !state.isNotNull(value); + } + boolean notNullable = state.checkNotNullable(value); - if (notNullable && problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) { + if (notNullable && + problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter && + problem != NullabilityProblem.passingNullToOptional) { DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory(); state.applyCondition(factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), NE, false)); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNotNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNotNullLiteral.java new file mode 100644 index 000000000000..966136953922 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNotNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.of()'" "true" +class A{ + void test(){ + com.google.common.base.Optional.of(11); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNullLiteral.java new file mode 100644 index 000000000000..e5599f2a86b6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterGuavaNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.absent()'" "true" +class A{ + void test(){ + com.google.common.base.Optional.absent(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullLiteral.java new file mode 100644 index 000000000000..bfe725293c8b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.of()'" "true" +class A{ + void test(){ + java.util.Optional.of(11); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullVariable.java new file mode 100644 index 000000000000..c1e85703be4b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNotNullVariable.java @@ -0,0 +1,8 @@ +// "Replace with '.of()'" "true" + +class A{ + void test(String s){ + assert s != null; + java.util.Optional.of(s); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullLiteral.java new file mode 100644 index 000000000000..850a7c3df1c8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullLiteral.java @@ -0,0 +1,7 @@ +// "Replace with '.empty()'" "true" +class A { + void test() { + String s = null; + java.util.Optional.empty(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullVariable.java new file mode 100644 index 000000000000..850a7c3df1c8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/afterNullVariable.java @@ -0,0 +1,7 @@ +// "Replace with '.empty()'" "true" +class A { + void test() { + String s = null; + java.util.Optional.empty(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNotNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNotNullLiteral.java new file mode 100644 index 000000000000..8c7c9be02f35 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNotNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.of()'" "true" +class A{ + void test(){ + com.google.common.base.Optional.fromNullable(11); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNullLiteral.java new file mode 100644 index 000000000000..fa25c555e2d0 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeGuavaNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.absent()'" "true" +class A{ + void test(){ + com.google.common.base.Optional.fromNullable(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullLiteral.java new file mode 100644 index 000000000000..5ef9972f2ca8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullLiteral.java @@ -0,0 +1,6 @@ +// "Replace with '.of()'" "true" +class A{ + void test(){ + java.util.Optional.ofNullable(11); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullVariable.java new file mode 100644 index 000000000000..53c9a43841f6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNotNullVariable.java @@ -0,0 +1,8 @@ +// "Replace with '.of()'" "true" + +class A{ + void test(String s){ + assert s != null; + java.util.Optional.ofNullable(s); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullLiteral.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullLiteral.java new file mode 100644 index 000000000000..b57dda49a134 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullLiteral.java @@ -0,0 +1,7 @@ +// "Replace with '.empty()'" "true" +class A { + void test() { + String s = null; + java.util.Optional.ofNullable(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullVariable.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullVariable.java new file mode 100644 index 000000000000..5f6d350318ae --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeNullVariable.java @@ -0,0 +1,7 @@ +// "Replace with '.empty()'" "true" +class A { + void test() { + String s = null; + java.util.Optional.ofNullable(s); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeUnknown.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeUnknown.java new file mode 100644 index 000000000000..6d506e8e8529 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable/beforeUnknown.java @@ -0,0 +1,6 @@ +// "Replace with '.of()'" "false" +class A{ + void test(String s){ + java.util.Optional.ofNullable(s); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ReplaceFromOfNullableFixTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ReplaceFromOfNullableFixTest.groovy new file mode 100644 index 000000000000..d1670967ab55 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/ReplaceFromOfNullableFixTest.groovy @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2015 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. + */ + +/* + * User: anna + * Date: 21-Mar-2008 + */ +package com.intellij.codeInsight.daemon.quickFix +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.dataFlow.DataFlowInspection +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.testFramework.IdeaTestUtil +import org.jetbrains.annotations.NotNull + +public class ReplaceFromOfNullableFixTest extends LightQuickFixParameterizedTestCase { + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return [new DataFlowInspection()] as LocalInspectionTool[] + } + + public void test() throws Exception { + doAllTests(); + } + + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/replaceFromOfNullable"; + } + + static void addGuavaOptional() { + if (JavaPsiFacade.getInstance(project).findClass("com.google.common.base.Optional", GlobalSearchScope.allScope(project))) { + return + } + + WriteCommandAction.runWriteCommandAction(project) { + VirtualFile optional = getSourceRoot() + .createChildDirectory(this, "com") + .createChildDirectory(this, "google") + .createChildDirectory(this, "common") + .createChildDirectory(this, "base") + .createChildData(this, "Optional.java"); + VfsUtil.saveText(optional, """ +package com.google.common.base; +public abstract class Optional { + public static Optional absent() { } + + public static Optional of(T reference) { } + + public static Optional fromNullable(@Nullable T nullableReference) { } +} +""") + } + } + + @Override + protected void beforeActionStarted(String testName, String contents) { + if (testName.contains("Guava")) { + addGuavaOptional() + } + super.beforeActionStarted(testName, contents) + } + + @Override + protected Sdk getProjectJDK() { + return IdeaTestUtil.getMockJdk18(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTestSuite.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTestSuite.java index 674a0f8cbb50..c7664bd15bc8 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTestSuite.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTestSuite.java @@ -17,6 +17,9 @@ package com.intellij.codeInspection; import com.intellij.codeInsight.completion.NormalCompletionDfaTest; import com.intellij.codeInsight.completion.SmartTypeCompletionDfaTest; +import com.intellij.codeInsight.daemon.quickFix.AddAssertStatementFixTest; +import com.intellij.codeInsight.daemon.quickFix.ReplaceFromOfNullableFixTest; +import com.intellij.codeInsight.daemon.quickFix.ReplaceWithOfNullableFixTest; import com.intellij.slicer.SliceBackwardTest; import com.intellij.slicer.SliceTreeTest; import junit.framework.Test; @@ -25,20 +28,29 @@ import junit.framework.TestSuite; public class DataFlowInspectionTestSuite { public static Test suite() { TestSuite suite = new TestSuite(); + suite.addTestSuite(DataFlowInspectionTest.class); suite.addTestSuite(DataFlowInspection8Test.class); suite.addTestSuite(DataFlowInspectionAncientTest.class); suite.addTestSuite(ContractCheckTest.class); + suite.addTestSuite(ContractInferenceFromSourceTest.class); suite.addTestSuite(NullityInferenceFromSourceTestCase.DfaInferenceTest.class); suite.addTestSuite(NullityInferenceFromSourceTestCase.LightInferenceTest.class); suite.addTestSuite(PurityInferenceFromSourceTest.class); + suite.addTestSuite(SliceTreeTest.class); suite.addTestSuite(SliceBackwardTest.class); + suite.addTestSuite(SmartTypeCompletionDfaTest.class); suite.addTestSuite(NormalCompletionDfaTest.class); + suite.addTestSuite(NullableStuffInspectionTest.class); suite.addTestSuite(NullableStuffInspection14Test.class); + + suite.addTestSuite(AddAssertStatementFixTest.class); + suite.addTestSuite(ReplaceWithOfNullableFixTest.class); + suite.addTestSuite(ReplaceFromOfNullableFixTest.class); return suite; } }