Add "Replace 'compute' with 'computeIfPresent'" fix

Helps for IDEA-194058 False-positive warning of producing NullPointerException for Map.compute
This commit is contained in:
Tagir Valeev
2018-06-19 15:07:31 +07:00
parent 42b2531f09
commit ecb2f6c1cf
7 changed files with 158 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2018 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;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import static com.intellij.util.ObjectUtils.tryCast;
public class ReplaceComputeWithComputeIfPresentFix implements LocalQuickFix, HighPriorityAction {
private static final CallMatcher MAP_COMPUTE = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "compute").
parameterTypes("K", CommonClassNames.JAVA_UTIL_FUNCTION_BI_FUNCTION);
@Override
@NotNull
public String getFamilyName() {
return InspectionsBundle.message("inspection.data.flow.use.computeifpresent.quickfix");
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiLambdaExpression.class);
if (lambda == null) return;
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(lambda, PsiMethodCallExpression.class);
if (call == null || !"compute".equals(call.getMethodExpression().getReferenceName())) return;
ExpressionUtils.bindCallTo(call, "computeIfPresent");
}
@Contract("null -> null")
public static ReplaceComputeWithComputeIfPresentFix makeFix(PsiElement reference) {
if (!(reference instanceof PsiReferenceExpression)) return null;
PsiParameter parameter = tryCast(((PsiReferenceExpression)reference).resolve(), PsiParameter.class);
if (parameter == null) return null;
PsiParameterList parameterList = tryCast(parameter.getParent(), PsiParameterList.class);
if (parameterList == null || parameterList.getParametersCount() != 2 || parameterList.getParameterIndex(parameter) != 1) return null;
PsiLambdaExpression lambda = tryCast(parameterList.getParent(), PsiLambdaExpression.class);
if (lambda == null) return null;
PsiExpressionList arguments = tryCast(lambda.getParent(), PsiExpressionList.class);
if (arguments == null || arguments.getExpressionCount() != 2 || arguments.getExpressions()[1] != lambda) return null;
PsiMethodCallExpression call = tryCast(arguments.getParent(), PsiMethodCallExpression.class);
if (!MAP_COMPUTE.test(call)) return null;
return new ReplaceComputeWithComputeIfPresentFix();
}
}

View File

@@ -119,6 +119,7 @@ public class DataFlowInspection extends DataFlowInspectionBase {
try {
ContainerUtil.addIfNotNull(fixes, StreamFilterNotNullFix.makeFix(qualifier));
ContainerUtil.addIfNotNull(fixes, ReplaceComputeWithComputeIfPresentFix.makeFix(qualifier));
if (isVolatileFieldReference(qualifier)) {
ContainerUtil.addIfNotNull(fixes, createIntroduceVariableFix(qualifier));
}

View File

@@ -0,0 +1,31 @@
// "Replace 'compute' with 'computeIfPresent'" "true"
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Main {
interface Item {
Integer getInfo(int i);
}
native List<String> getNamesList();
void test(List<Item> infoList) {
Map<String, List<Integer>> data = new HashMap<>();
List<String> names = getNamesList();
for (String key : names) {
data.put(key, new ArrayList<>());
}
for (Item info : infoList) {
for (int i = 0; i < names.size(); i++) {
final String k = names.get(i);
final Integer newValue = info.getInfo(i);
data.computeIfPresent(k, (key, array) -> {
array.add(newValue);
return array;
});
}
}
}
}

View File

@@ -0,0 +1,31 @@
// "Replace 'compute' with 'computeIfPresent'" "true"
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Main {
interface Item {
Integer getInfo(int i);
}
native List<String> getNamesList();
void test(List<Item> infoList) {
Map<String, List<Integer>> data = new HashMap<>();
List<String> names = getNamesList();
for (String key : names) {
data.put(key, new ArrayList<>());
}
for (Item info : infoList) {
for (int i = 0; i < names.size(); i++) {
final String k = names.get(i);
final Integer newValue = info.getInfo(i);
data.compute(k, (key, array) -> {
array.a<caret>dd(newValue);
return array;
});
}
}
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2000-2018 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.codeInsight.daemon.quickFix;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ReplaceComputeWithComputeIfPresentFixTest extends LightQuickFixParameterizedTestCase {
private static final LightProjectDescriptor DESCRIPTOR = new LightProjectDescriptor() {
@Nullable
@Override
public Sdk getSdk() {
return PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk18());
}
};
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{new DataFlowInspection()};
}
public void test() {
doAllTests();
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return DESCRIPTOR;
}
@Override
protected String getBasePath() {
return "/codeInsight/daemonCodeAnalyzer/quickFix/replaceComputeWithComputeIfPresent";
}
}

View File

@@ -66,6 +66,7 @@ public class DataFlowInspectionTestSuite {
suite.addTestSuite(UnwrapIfStatementFixTest.class);
suite.addTestSuite(StreamFilterNotNullFixTest.class);
suite.addTestSuite(RedundantInstanceofFixTest.class);
suite.addTestSuite(ReplaceComputeWithComputeIfPresentFixTest.class);
return suite;
}
}

View File

@@ -51,6 +51,7 @@ inspection.data.flow.redundant.instanceof.quickfix=Replace with a null check
inspection.data.flow.simplify.boolean.expression.quickfix=Simplify boolean expression
inspection.data.flow.simplify.to.assignment.quickfix.name=Simplify to normal assignment
inspection.data.flow.filter.notnull.quickfix=Insert 'filter(Objects::nonNull)' step
inspection.data.flow.use.computeifpresent.quickfix=Replace 'compute' with 'computeIfPresent'
configure.annotations.option=Configure annotations
#messages from dataflow inspection