IDEA-191905 Optional.get without Optional.isPresent can suggest quick fix when Optional.get returns Optional

This commit is contained in:
Tagir Valeev
2018-05-29 17:58:05 +07:00
parent 49cc316a3d
commit 770acf5b8f
4 changed files with 191 additions and 4 deletions
@@ -2,18 +2,28 @@
package com.intellij.codeInspection.java18api;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
import com.intellij.codeInspection.InspectionsBundle;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.dataFlow.CommonDataflow;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.DfaOptionalSupport;
import com.intellij.codeInspection.util.LambdaGenerationUtil;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class OptionalGetWithoutIsPresentInspection extends AbstractBaseJavaLocalInspectionTool {
@NotNull
@Override
@@ -37,7 +47,8 @@ public class OptionalGetWithoutIsPresentInspection extends AbstractBaseJavaLocal
result.getExpressionFact(qualifier, DfaFactType.OPTIONAL_PRESENCE) == null &&
!isPresentCallWithSameQualifierExists(qualifier)) {
holder.registerProblem(nameElement,
InspectionsBundle.message("inspection.optional.get.without.is.present.message", optionalClass.getName()));
InspectionsBundle.message("inspection.optional.get.without.is.present.message", optionalClass.getName()),
tryCreateFix(call));
}
}
}
@@ -60,4 +71,73 @@ public class OptionalGetWithoutIsPresentInspection extends AbstractBaseJavaLocal
}
};
}
private static LocalQuickFix tryCreateFix(PsiMethodCallExpression call) {
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return null;
PsiClass optionalClass = PsiUtil.resolveClassInClassTypeOnly(qualifier.getType());
if (optionalClass == null || !CommonClassNames.JAVA_UTIL_OPTIONAL.equals(optionalClass.getQualifiedName())) return null;
PsiType optionalElementType = OptionalUtil.getOptionalElementType(qualifier.getType());
if (optionalElementType == null) return null;
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(call);
if (nextCall != null) {
if (optionalClass.equals(PsiUtil.resolveClassInClassTypeOnly(nextCall.getType()))) {
if (!LambdaGenerationUtil.canBeUncheckedLambda(nextCall)) {
// Probably qualifier accesses non-final vars or throws exception: we will replace qualifier, so this is not a problem
PsiMethodCallExpression copy = (PsiMethodCallExpression)nextCall.copy();
PsiExpression copyQualifier = Objects.requireNonNull(copy.getMethodExpression().getQualifierExpression());
try {
copyQualifier.replace(JavaPsiFacade.getElementFactory(call.getProject())
.createExpressionFromText("((" + optionalElementType.getCanonicalText() + ")null)",
copyQualifier));
}
catch (IncorrectOperationException e) {
return null;
}
if (!LambdaGenerationUtil.canBeUncheckedLambda(copy)) {
return null;
}
}
return new UseFlatMapFix();
}
}
return null;
}
private static class UseFlatMapFix implements LocalQuickFix {
@Nls(capitalization = Nls.Capitalization.Sentence)
@NotNull
@Override
public String getFamilyName() {
return "Use 'flatMap'";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
if (call == null) return;
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
if (qualifier == null) return;
PsiType elementType = OptionalUtil.getOptionalElementType(qualifier.getType());
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(call);
if (nextCall == null) return;
JavaCodeStyleManager manager = JavaCodeStyleManager.getInstance(project);
SuggestedNameInfo info = manager.suggestVariableName(VariableKind.PARAMETER, null, qualifier, elementType, true);
String name = info.names.length == 0 ? "value" : info.names[0];
name = manager.suggestUniqueVariableName(name, call, true);
CommentTracker ct = new CommentTracker();
PsiReferenceExpression methodExpression = nextCall.getMethodExpression();
ct.markRangeUnchanged(Objects.requireNonNull(methodExpression.getQualifierExpression()).getNextSibling(),
methodExpression.getLastChild());
ct.markRangeUnchanged(methodExpression.getNextSibling(), nextCall.getLastChild());
PsiMethodCallExpression newNextCall = (PsiMethodCallExpression)nextCall.copy();
PsiExpression newQualifier = Objects.requireNonNull(newNextCall.getMethodExpression().getQualifierExpression());
newQualifier.replace(JavaPsiFacade.getElementFactory(project).createExpressionFromText(name, newNextCall));
String lambda = name + "->" + newNextCall.getText();
String replacement = ct.text(qualifier) + ".flatMap(" + lambda + ")";
PsiMethodCallExpression result = (PsiMethodCallExpression)ct.replaceAndRestoreComments(nextCall, replacement);
LambdaCanBeMethodReferenceInspection.replaceLambdaWithMethodReference(
(PsiLambdaExpression)result.getArgumentList().getExpressions()[0]);
}
}
}
@@ -0,0 +1,37 @@
// "Fix all 'Optional.get() is called without isPresent() check' problems in file" "true"
import java.util.Optional;
class Test {
class PropertyHolder<T> {
T property;
Optional<T> getProperty() {
return Optional.ofNullable(property);
}
Optional<T> getProperty(String s) {
return Optional.ofNullable(property);
}
Optional<T> getPropertyEx() throws Exception {
return Optional.ofNullable(property);
}
}
class Smth<T> {
PropertyHolder<T> propertyHolder;
Optional<PropertyHolder<T>> getPropertyHolder() {
return Optional.ofNullable(propertyHolder);
}
Optional<PropertyHolder<T>> getPropertyHolderEx() throws Exception {
return Optional.ofNullable(propertyHolder);
}
}
void test() throws Exception {
Optional<Long> property = new Smth<Long>().getPropertyHolder().flatMap(PropertyHolder::getProperty);
Optional<Long> property1 = new Smth<Long>().getPropertyHolderEx().flatMap(propertyHolderEx -> propertyHolderEx.getProperty("foo"));
Optional<Long> property2 = new Smth<Long>().getPropertyHolderEx().get().getPropertyEx();
}
}
@@ -0,0 +1,37 @@
// "Fix all 'Optional.get() is called without isPresent() check' problems in file" "true"
import java.util.Optional;
class Test {
class PropertyHolder<T> {
T property;
Optional<T> getProperty() {
return Optional.ofNullable(property);
}
Optional<T> getProperty(String s) {
return Optional.ofNullable(property);
}
Optional<T> getPropertyEx() throws Exception {
return Optional.ofNullable(property);
}
}
class Smth<T> {
PropertyHolder<T> propertyHolder;
Optional<PropertyHolder<T>> getPropertyHolder() {
return Optional.ofNullable(propertyHolder);
}
Optional<PropertyHolder<T>> getPropertyHolderEx() throws Exception {
return Optional.ofNullable(propertyHolder);
}
}
void test() throws Exception {
Optional<Long> property = new Smth<Long>().getPropertyHolder().ge<caret>t().getProperty();
Optional<Long> property1 = new Smth<Long>().getPropertyHolderEx().get().getProperty("foo");
Optional<Long> property2 = new Smth<Long>().getPropertyHolderEx().get().getPropertyEx();
}
}
@@ -0,0 +1,33 @@
// 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.codeInspection;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.java18api.OptionalGetWithoutIsPresentInspection;
import com.intellij.testFramework.LightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
import static com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase.JAVA_8;
public class OptionalGetWithoutIsPresentInspectionFixTest extends LightQuickFixParameterizedTestCase {
public void test() { doAllTests(); }
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_8;
}
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
return new LocalInspectionTool[]{
new OptionalGetWithoutIsPresentInspection()
};
}
@Override
protected String getBasePath() {
return "/inspection/optionalGet";
}
}