mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
improve optional chain simplification
This commit is contained in:
+444
-91
@@ -15,25 +15,29 @@
|
||||
*/
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
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.impl.PsiDiamondTypeUtil;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiExpressionTrimRenderer;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.util.LambdaRefactoringUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.EquivalenceChecker;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import com.siyeh.ig.psiutils.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.intellij.util.ObjectUtils.tryCast;
|
||||
|
||||
|
||||
/**
|
||||
* @author Tagir Valeev
|
||||
*/
|
||||
@@ -54,88 +58,113 @@ public class SimplifyOptionalCallChainsInspection extends BaseJavaBatchLocalInsp
|
||||
if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) {
|
||||
return PsiElementVisitor.EMPTY_VISITOR;
|
||||
}
|
||||
return new JavaElementVisitor() {
|
||||
return new OptionalChainVisitor() {
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression call) {
|
||||
PsiExpression falseArg = null;
|
||||
boolean useOrElseGet = false;
|
||||
if (OPTIONAL_OR_ELSE.test(call)) {
|
||||
falseArg = call.getArgumentList().getExpressions()[0];
|
||||
}
|
||||
else if (OPTIONAL_OR_ELSE_GET.test(call)) {
|
||||
useOrElseGet = true;
|
||||
PsiLambdaExpression lambda = getLambda(call.getArgumentList().getExpressions()[0]);
|
||||
if (lambda == null || lambda.getParameterList().getParametersCount() != 0) return;
|
||||
falseArg = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
}
|
||||
if (falseArg == null) return;
|
||||
handleMapOrElse(call, useOrElseGet, falseArg);
|
||||
handleOfNullableOrElse(call, falseArg);
|
||||
}
|
||||
|
||||
private void handleOfNullableOrElse(PsiMethodCallExpression call, PsiExpression falseArg) {
|
||||
if (!ExpressionUtils.isNullLiteral(falseArg)) return;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(call.getParent());
|
||||
if (!(parent instanceof PsiExpressionList)) return;
|
||||
PsiMethodCallExpression parentCall = ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (!OPTIONAL_OF_NULLABLE.test(parentCall)) return;
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier == null ||
|
||||
!EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(qualifier.getType(), parentCall.getType())) {
|
||||
return;
|
||||
}
|
||||
holder.registerProblem(Objects.requireNonNull(parentCall.getMethodExpression().getReferenceNameElement()),
|
||||
"Unnecessary Optional rewrapping",
|
||||
new SimplifyOptionalChainFix(qualifier.getText(), "Unwrap"));
|
||||
}
|
||||
|
||||
private void handleMapOrElse(PsiMethodCallExpression call, boolean useOrElseGet, PsiExpression falseArg) {
|
||||
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
if (!OPTIONAL_MAP.test(qualifierCall)) return;
|
||||
PsiLambdaExpression lambda = getLambda(qualifierCall.getArgumentList().getExpressions()[0]);
|
||||
if (lambda == null) return;
|
||||
PsiExpression trueArg = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (trueArg == null) return;
|
||||
PsiParameter[] parameters = lambda.getParameterList().getParameters();
|
||||
if (parameters.length != 1) return;
|
||||
PsiExpression qualifier = qualifierCall.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier == null) return;
|
||||
String opt = qualifier.getText();
|
||||
PsiParameter parameter = parameters[0];
|
||||
String proposed = OptionalUtil.generateOptionalUnwrap(opt, parameter, trueArg, falseArg, call.getType(), useOrElseGet);
|
||||
String canonicalOrElse;
|
||||
if (useOrElseGet && !ExpressionUtils.isSimpleExpression(falseArg)) {
|
||||
canonicalOrElse = ".orElseGet(() -> " + falseArg.getText() + ")";
|
||||
}
|
||||
else {
|
||||
canonicalOrElse = ".orElse(" + falseArg.getText() + ")";
|
||||
}
|
||||
String canonical = opt + ".map(" + LambdaUtil.createLambda(parameter, trueArg) + ")" + canonicalOrElse;
|
||||
if (proposed.length() < canonical.length()) {
|
||||
String displayCode;
|
||||
if(proposed.equals(opt)) {
|
||||
displayCode = "";
|
||||
} else if(opt.length() > 10) {
|
||||
// should be a parseable expression
|
||||
opt = "(($))";
|
||||
String template = OptionalUtil.generateOptionalUnwrap(opt, parameter, trueArg, falseArg, call.getType(), useOrElseGet);
|
||||
displayCode =
|
||||
PsiExpressionTrimRenderer.render(JavaPsiFacade.getElementFactory(holder.getProject()).createExpressionFromText(template, call));
|
||||
displayCode = displayCode.replaceFirst(Pattern.quote(opt), "..");
|
||||
} else {
|
||||
displayCode =
|
||||
PsiExpressionTrimRenderer.render(JavaPsiFacade.getElementFactory(holder.getProject()).createExpressionFromText(proposed, call));
|
||||
}
|
||||
String message = displayCode.isEmpty() ? "Remove redundant steps from optional chain" :
|
||||
"Simplify optional chain to '" + displayCode + "'";
|
||||
holder.registerProblem(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()),
|
||||
"Optional chain can be simplified",
|
||||
new SimplifyOptionalChainFix(proposed, message));
|
||||
}
|
||||
protected void handleSimplification(@NotNull PsiElement element,
|
||||
@NotNull OptionalChainSimplification simplification) {
|
||||
holder.registerProblem(element, simplification.getDescription(), new OptionalChainFix(simplification));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static abstract class OptionalChainVisitor extends JavaElementVisitor {
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression call) {
|
||||
PsiExpression falseArg = null;
|
||||
boolean useOrElseGet = false;
|
||||
if (OPTIONAL_OR_ELSE.test(call)) {
|
||||
falseArg = call.getArgumentList().getExpressions()[0];
|
||||
}
|
||||
else if (OPTIONAL_OR_ELSE_GET.test(call)) {
|
||||
useOrElseGet = true;
|
||||
PsiLambdaExpression lambda = getLambda(call.getArgumentList().getExpressions()[0]);
|
||||
if (lambda == null || lambda.getParameterList().getParametersCount() != 0) return;
|
||||
falseArg = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
}
|
||||
if (falseArg == null) return;
|
||||
handleMapOrElse(call, useOrElseGet, falseArg);
|
||||
handleOfNullableOrElse(call, falseArg);
|
||||
handleOrElseNullConditionalReturn(call, falseArg);
|
||||
handleOrElseNullConditionalAction(call, falseArg);
|
||||
}
|
||||
|
||||
private void handleOfNullableOrElse(PsiMethodCallExpression call, PsiExpression falseArg) {
|
||||
if (!ExpressionUtils.isNullLiteral(falseArg)) return;
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(call.getParent());
|
||||
if (!(parent instanceof PsiExpressionList)) return;
|
||||
PsiMethodCallExpression parentCall = tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (!OPTIONAL_OF_NULLABLE.test(parentCall)) return;
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier == null ||
|
||||
!EquivalenceChecker.getCanonicalPsiEquivalence().typesAreEquivalent(qualifier.getType(), parentCall.getType())) {
|
||||
return;
|
||||
}
|
||||
SimplifyOptionalChainFix fix = new SimplifyOptionalChainFix(qualifier.getText(), "Unwrap", "Unnecessary Optional rewrapping");
|
||||
handleSimplification(Objects.requireNonNull(parentCall.getMethodExpression().getReferenceNameElement()), fix);
|
||||
}
|
||||
|
||||
private void handleMapOrElse(PsiMethodCallExpression call, boolean useOrElseGet, PsiExpression falseArg) {
|
||||
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
if (!OPTIONAL_MAP.test(qualifierCall)) return;
|
||||
PsiLambdaExpression lambda = getLambda(qualifierCall.getArgumentList().getExpressions()[0]);
|
||||
if (lambda == null) return;
|
||||
PsiExpression trueArg = LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
if (trueArg == null) return;
|
||||
PsiParameter[] parameters = lambda.getParameterList().getParameters();
|
||||
if (parameters.length != 1) return;
|
||||
PsiExpression qualifier = qualifierCall.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier == null) return;
|
||||
String opt = qualifier.getText();
|
||||
PsiParameter parameter = parameters[0];
|
||||
String proposed = OptionalUtil.generateOptionalUnwrap(opt, parameter, trueArg, falseArg, call.getType(), useOrElseGet);
|
||||
String canonicalOrElse;
|
||||
if (useOrElseGet && !ExpressionUtils.isSimpleExpression(falseArg)) {
|
||||
canonicalOrElse = ".orElseGet(() -> " + falseArg.getText() + ")";
|
||||
}
|
||||
else {
|
||||
canonicalOrElse = ".orElse(" + falseArg.getText() + ")";
|
||||
}
|
||||
String canonical = opt + ".map(" + LambdaUtil.createLambda(parameter, trueArg) + ")" + canonicalOrElse;
|
||||
if (proposed.length() < canonical.length()) {
|
||||
String displayCode;
|
||||
if(proposed.equals(opt)) {
|
||||
displayCode = "";
|
||||
} else if(opt.length() > 10) {
|
||||
// should be a parseable expression
|
||||
opt = "(($))";
|
||||
String template = OptionalUtil.generateOptionalUnwrap(opt, parameter, trueArg, falseArg, call.getType(), useOrElseGet);
|
||||
displayCode =
|
||||
PsiExpressionTrimRenderer.render(JavaPsiFacade.getElementFactory(parameter.getProject()).createExpressionFromText(template, call));
|
||||
displayCode = displayCode.replaceFirst(Pattern.quote(opt), "..");
|
||||
} else {
|
||||
displayCode =
|
||||
PsiExpressionTrimRenderer.render(JavaPsiFacade.getElementFactory(parameter.getProject()).createExpressionFromText(proposed, call));
|
||||
}
|
||||
String message = displayCode.isEmpty() ? "Remove redundant steps from optional chain" :
|
||||
"Simplify optional chain to '" + displayCode + "'";
|
||||
SimplifyOptionalChainFix fix = new SimplifyOptionalChainFix(proposed, message, "Optional chain can be simplified");
|
||||
handleSimplification(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()), fix);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOrElseNullConditionalReturn(PsiMethodCallExpression call, PsiExpression falseArg) {
|
||||
OrElseReturnStreamFix.Context context = OrElseReturnStreamFix.Context.extract(call, falseArg);
|
||||
if (context == null) return;
|
||||
OrElseReturnStreamFix fix = new OrElseReturnStreamFix(context.getDefaultExpression(), context.isSimple());
|
||||
handleSimplification(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()), fix);
|
||||
}
|
||||
|
||||
private void handleOrElseNullConditionalAction(PsiMethodCallExpression call, PsiExpression falseArg) {
|
||||
if (OrElseNonNullActionFix.Context.extract(call, falseArg) == null) return;
|
||||
OrElseNonNullActionFix fix = new OrElseNonNullActionFix();
|
||||
handleSimplification(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()), fix);
|
||||
}
|
||||
|
||||
protected abstract void handleSimplification(@NotNull PsiElement element, @NotNull OptionalChainSimplification simplification);
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static PsiLambdaExpression getLambda(PsiExpression initializer) {
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(initializer);
|
||||
if (expression instanceof PsiLambdaExpression) {
|
||||
@@ -147,20 +176,41 @@ public class SimplifyOptionalCallChainsInspection extends BaseJavaBatchLocalInsp
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class SimplifyOptionalChainFix implements LocalQuickFix {
|
||||
private final String myReplacement;
|
||||
private final String myMessage;
|
||||
|
||||
public SimplifyOptionalChainFix(String replacement, String message) {
|
||||
myReplacement = replacement;
|
||||
myMessage = message;
|
||||
@Nullable
|
||||
private static PsiExpression extractFalseArg(@NotNull PsiMethodCallExpression call) {
|
||||
if (OPTIONAL_OR_ELSE.test(call)) {
|
||||
return call.getArgumentList().getExpressions()[0];
|
||||
}
|
||||
if (OPTIONAL_OR_ELSE_GET.test(call)) {
|
||||
PsiLambdaExpression lambda = getLambda(call.getArgumentList().getExpressions()[0]);
|
||||
if (lambda == null || lambda.getParameterList().getParametersCount() != 0) return null;
|
||||
return LambdaUtil.extractSingleExpressionFromBody(lambda.getBody());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
interface OptionalChainSimplification {
|
||||
@NotNull
|
||||
String getName();
|
||||
|
||||
@NotNull
|
||||
String getDescription();
|
||||
|
||||
void applyFix(@NotNull Project project, @NotNull PsiElement element);
|
||||
}
|
||||
|
||||
|
||||
private static class OptionalChainFix implements LocalQuickFix {
|
||||
|
||||
private final @NotNull OptionalChainSimplification mySimplification;
|
||||
|
||||
OptionalChainFix(@NotNull OptionalChainSimplification simplification) {mySimplification = simplification;}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return myMessage;
|
||||
return mySimplification.getName();
|
||||
}
|
||||
|
||||
@Nls
|
||||
@@ -172,7 +222,310 @@ public class SimplifyOptionalCallChainsInspection extends BaseJavaBatchLocalInsp
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class);
|
||||
mySimplification.applyFix(project, descriptor.getStartElement());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class OrElseReturnStreamFix implements OptionalChainSimplification {
|
||||
private final @NotNull String defaultExpression;
|
||||
private final boolean myIsSimple;
|
||||
|
||||
private OrElseReturnStreamFix(@NotNull PsiExpression expression, boolean simple) {
|
||||
defaultExpression = PsiExpressionTrimRenderer.render(expression);
|
||||
myIsSimple = simple;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
String method = myIsSimple ? "orElse" : "orElseGet";
|
||||
return "Replace null check with " + method + "(" + defaultExpression + ")";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Remove redundant null check";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull PsiElement element) {
|
||||
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class, false);
|
||||
if (call == null) return;
|
||||
PsiExpression falseArg = extractFalseArg(call);
|
||||
if (!ExpressionUtils.isNullLiteral(falseArg)) return;
|
||||
Context context = Context.extract(call, falseArg);
|
||||
if (context == null) return;
|
||||
PsiExpression receiver = context.getOrElseCall().getMethodExpression().getQualifierExpression();
|
||||
if (receiver == null) return;
|
||||
String methodWithArg = context.isSimple()
|
||||
? ".orElse(" + context.getDefaultExpression().getText() + ")"
|
||||
: ".orElseGet(()->" + context.getDefaultExpression().getText() + ")";
|
||||
String expressionText;
|
||||
expressionText = receiver.getText() + methodWithArg;
|
||||
PsiStatement finalStatement = JavaPsiFacade.getElementFactory(project).createStatementFromText("return " + expressionText + ";", receiver);
|
||||
PsiStatement current = PsiTreeUtil.getParentOfType(context.getOrElseCall(), PsiStatement.class, false);
|
||||
if (current == null) return;
|
||||
PsiElement result = new CommentTracker().replaceAndRestoreComments(current, finalStatement);
|
||||
new CommentTracker().deleteAndRestoreComments(context.getNextStatement());
|
||||
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
|
||||
}
|
||||
|
||||
/*
|
||||
if(optValue != null) {return optValue;} else {return "default";}
|
||||
or
|
||||
return optValue == null? "default" : optValue;
|
||||
*/
|
||||
@Nullable
|
||||
private static PsiExpression extractConditionalDefaultValue(@NotNull PsiStatement statement, @NotNull PsiVariable optValue) {
|
||||
if (statement instanceof PsiIfStatement) {
|
||||
PsiIfStatement ifStatement = (PsiIfStatement)statement;
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
if (condition == null) return null;
|
||||
PsiExpression thenExpr = getReturnExpression(ifStatement.getThenBranch());
|
||||
PsiExpression elseExpr = getReturnExpression(ifStatement.getElseBranch());
|
||||
if (thenExpr == null || elseExpr == null) return null;
|
||||
return extractConditionalDefaultValue(thenExpr, elseExpr, condition, optValue);
|
||||
}
|
||||
else if (statement instanceof PsiReturnStatement) {
|
||||
PsiExpression returnValue = ((PsiReturnStatement)statement).getReturnValue();
|
||||
PsiConditionalExpression ternary = tryCast(PsiUtil.skipParenthesizedExprDown(returnValue), PsiConditionalExpression.class);
|
||||
if (ternary == null) return null;
|
||||
PsiExpression thenExpression = ternary.getThenExpression();
|
||||
PsiExpression elseExpression = ternary.getElseExpression();
|
||||
if(thenExpression == null || elseExpression == null) return null;
|
||||
return extractConditionalDefaultValue(thenExpression, elseExpression, ternary.getCondition(), optValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Contract("null -> null")
|
||||
@Nullable
|
||||
private static PsiExpression getReturnExpression(@Nullable PsiStatement block) {
|
||||
if (block == null) return null;
|
||||
PsiStatement statement = ControlFlowUtils.stripBraces(block);
|
||||
PsiReturnStatement returnStatement = tryCast(statement, PsiReturnStatement.class);
|
||||
if (returnStatement == null) return null;
|
||||
return returnStatement.getReturnValue();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiExpression extractConditionalDefaultValue(@NotNull PsiExpression thenExpr,
|
||||
@NotNull PsiExpression elseExpr,
|
||||
@NotNull PsiExpression condition,
|
||||
@NotNull PsiVariable optValue) {
|
||||
PsiVariable nullChecked = ExpressionUtils.getVariableFromNullComparison(condition, true);
|
||||
boolean inverted = false;
|
||||
if (nullChecked == null) {
|
||||
nullChecked = ExpressionUtils.getVariableFromNullComparison(condition, false);
|
||||
if (nullChecked == null) return null;
|
||||
inverted = true;
|
||||
}
|
||||
if (!nullChecked.equals(optValue) || !ExpressionUtils.isReferenceTo(inverted ? thenExpr : elseExpr, optValue)) return null;
|
||||
PsiExpression defaultExpression = inverted ? elseExpr : thenExpr;
|
||||
if (VariableAccessUtils.variableIsUsed(optValue, defaultExpression)) return null;
|
||||
return defaultExpression;
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
@NotNull private final PsiMethodCallExpression myOrElseCall;
|
||||
@NotNull private final PsiExpression myDefaultExpression;
|
||||
@NotNull private final PsiStatement myNextStatement;
|
||||
private final boolean mySimple;
|
||||
|
||||
private Context(@NotNull PsiMethodCallExpression call,
|
||||
@NotNull PsiExpression defaultExpression,
|
||||
@NotNull PsiStatement nextStatement, boolean simple) {
|
||||
myOrElseCall = call;
|
||||
myDefaultExpression = defaultExpression;
|
||||
myNextStatement = nextStatement;
|
||||
mySimple = simple;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiStatement getNextStatement() {
|
||||
return myNextStatement;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public PsiMethodCallExpression getOrElseCall() {
|
||||
return myOrElseCall;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiExpression getDefaultExpression() {
|
||||
return myDefaultExpression;
|
||||
}
|
||||
|
||||
public boolean isSimple() {
|
||||
return mySimple;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Context extract(@NotNull PsiMethodCallExpression call, @NotNull PsiExpression falseArg) {
|
||||
if (!ExpressionUtils.isNullLiteral(falseArg)) return null;
|
||||
PsiLocalVariable returnVar = PsiTreeUtil.getParentOfType(call, PsiLocalVariable.class, true);
|
||||
if (returnVar == null) return null;
|
||||
PsiStatement nextStatement =
|
||||
tryCast(PsiTreeUtil.skipWhitespacesForward(returnVar.getParent()), PsiStatement.class);
|
||||
if (nextStatement == null) return null;
|
||||
PsiExpression defaultValue = extractConditionalDefaultValue(nextStatement, returnVar);
|
||||
boolean isSimple = ExpressionUtils.isSimpleExpression(defaultValue);
|
||||
if (defaultValue == null || (!isSimple && !LambdaGenerationUtil.canBeUncheckedLambda(defaultValue))) return null;
|
||||
PsiType type = defaultValue.getType();
|
||||
PsiType methodCallReturnValue = call.getMethodExpression().getType();
|
||||
if(type == null || methodCallReturnValue == null || !methodCallReturnValue.isAssignableFrom(type)) return null;
|
||||
return new Context(call, defaultValue, nextStatement, isSimple);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class OrElseNonNullActionFix implements OptionalChainSimplification {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Replace null check with ifPresent()";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Remove redundant null check";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull PsiElement element) {
|
||||
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class, false);
|
||||
if (call == null) return;
|
||||
PsiExpression falseArg = extractFalseArg(call);
|
||||
if (!ExpressionUtils.isNullLiteral(falseArg)) return;
|
||||
Context context = Context.extract(call, falseArg);
|
||||
if(context == null) return;
|
||||
PsiExpression receiver = context.getOrElseCall().getMethodExpression().getQualifierExpression();
|
||||
if(receiver == null) return;
|
||||
String statementText = receiver.getText() + ".ifPresent(" + LambdaUtil.createLambda(context.getVariable(), context.getAction()) + ");";
|
||||
PsiStatement finalStatement = JavaPsiFacade.getElementFactory(project).createStatementFromText(statementText, context.getStatement());
|
||||
PsiElement result = context.getStatement().replace(finalStatement);
|
||||
context.getConditionStatement().delete();
|
||||
LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result);
|
||||
}
|
||||
|
||||
private static class Context {
|
||||
private final @NotNull PsiExpression myAction;
|
||||
private final @NotNull PsiStatement myConditionStatement;
|
||||
private final @NotNull PsiStatement myStatement;
|
||||
private final @NotNull PsiVariable myVariable;
|
||||
private final @NotNull PsiMethodCallExpression myOrElseCall;
|
||||
|
||||
@NotNull
|
||||
public PsiExpression getAction() {
|
||||
return myAction;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiStatement getConditionStatement() {
|
||||
return myConditionStatement;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiStatement getStatement() {
|
||||
return myStatement;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiMethodCallExpression getOrElseCall() {
|
||||
return myOrElseCall;
|
||||
}
|
||||
|
||||
private Context(@NotNull PsiExpression action,
|
||||
@NotNull PsiStatement conditionStatement,
|
||||
@NotNull PsiStatement statement,
|
||||
@NotNull PsiVariable variable, @NotNull PsiMethodCallExpression call) {
|
||||
myAction = action;
|
||||
myConditionStatement = conditionStatement;
|
||||
myStatement = statement;
|
||||
myVariable = variable;
|
||||
myOrElseCall = call;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiVariable getVariable() {
|
||||
return myVariable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static Context extract(@NotNull PsiMethodCallExpression orElseCall, @NotNull PsiExpression orElseArgument) {
|
||||
if (!ExpressionUtils.isNullLiteral(orElseArgument)) return null;
|
||||
PsiLocalVariable returnVar = PsiTreeUtil.getParentOfType(orElseCall, PsiLocalVariable.class, true);
|
||||
if (returnVar == null) return null;
|
||||
PsiStatement statement = PsiTreeUtil.getParentOfType(returnVar, PsiStatement.class, false);
|
||||
if(statement == null) return null;
|
||||
PsiStatement nextStatement =
|
||||
tryCast(PsiTreeUtil.skipWhitespacesForward(returnVar.getParent()), PsiStatement.class);
|
||||
if (nextStatement == null) return null;
|
||||
PsiExpression lambdaExpr = extractMappingExpression(nextStatement, returnVar);
|
||||
if (lambdaExpr == null || !LambdaGenerationUtil.canBeUncheckedLambda(lambdaExpr)) return null;
|
||||
if(!ReferencesSearch.search(returnVar).forEach(reference ->
|
||||
PsiTreeUtil.isAncestor(statement, reference.getElement(), false) ||
|
||||
PsiTreeUtil.isAncestor(nextStatement, reference.getElement(), false))) return null;
|
||||
return new Context(lambdaExpr, nextStatement, statement, returnVar, orElseCall);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if(optValue != null) {
|
||||
System.out.println(optValue);
|
||||
}
|
||||
*/
|
||||
@Nullable
|
||||
private static PsiExpression extractMappingExpression(@NotNull PsiStatement statement, @NotNull PsiVariable optValue) {
|
||||
PsiIfStatement ifStatement = tryCast(statement, PsiIfStatement.class);
|
||||
if (ifStatement == null) return null;
|
||||
if (ifStatement.getElseBranch() != null) return null;
|
||||
PsiExpression condition = ifStatement.getCondition();
|
||||
if (condition == null) return null;
|
||||
if (ExpressionUtils.getVariableFromNullComparison(condition, false) != optValue) return null;
|
||||
|
||||
PsiStatement thenStatement = ControlFlowUtils.stripBraces(ifStatement.getThenBranch());
|
||||
PsiExpressionStatement expressionStatement = tryCast(thenStatement, PsiExpressionStatement.class);
|
||||
if (expressionStatement == null) return null;
|
||||
return expressionStatement.getExpression();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class SimplifyOptionalChainFix implements OptionalChainSimplification {
|
||||
private final String myReplacement;
|
||||
private final String myMessage;
|
||||
private final String myDescription;
|
||||
|
||||
public SimplifyOptionalChainFix(String replacement, String message, String description) {
|
||||
myReplacement = replacement;
|
||||
myMessage = message;
|
||||
myDescription = description;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return myMessage;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull PsiElement element) {
|
||||
PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
|
||||
if (call == null) return;
|
||||
PsiExpression replacementExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myReplacement, call);
|
||||
PsiElement result = call.replace(replacementExpression);
|
||||
|
||||
+1
-76
@@ -83,8 +83,6 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
instanceCall(CommonClassNames.JAVA_LANG_BOOLEAN, "equals").parameterCount(1);
|
||||
private static final CallMatcher STREAM_OF =
|
||||
staticCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T");
|
||||
private static final CallMatcher OR_ELSE =
|
||||
instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1);
|
||||
|
||||
private static final CallMatcher STREAM_MATCH = anyOf(STREAM_ANY_MATCH, STREAM_NONE_MATCH, STREAM_ALL_MATCH);
|
||||
|
||||
@@ -97,8 +95,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
ReplaceForEachMethodFix.handler(),
|
||||
RemoveBooleanIdentityFix.handler(),
|
||||
ReplaceWithPeekFix.handler(),
|
||||
SimpleStreamOfFix.handler(),
|
||||
OrElseReturnStreamFix.handler()
|
||||
SimpleStreamOfFix.handler()
|
||||
).registerAll(SimplifyMatchNegationFix.handlers());
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(SimplifyStreamApiCallChainsInspection.class);
|
||||
@@ -1435,76 +1432,4 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrElseReturnStreamFix implements CallChainSimplification {
|
||||
|
||||
@NotNull private final PsiLocalVariable myReturnVar;
|
||||
@NotNull private final PsiExpression myReturnExpr;
|
||||
@NotNull private final PsiExpression myOrElseArgument;
|
||||
@NotNull private final PsiMethodCallExpression myOrElseCall;
|
||||
@NotNull private final PsiExpression myDefaultExpression;
|
||||
|
||||
public OrElseReturnStreamFix(@NotNull PsiLocalVariable returnVar,
|
||||
@NotNull PsiExpression returnExpr,
|
||||
@NotNull PsiExpression orElseArgument,
|
||||
@NotNull PsiMethodCallExpression orElseCall,
|
||||
@NotNull PsiExpression defaultExpression) {
|
||||
myReturnVar = returnVar;
|
||||
myReturnExpr = returnExpr;
|
||||
myOrElseArgument = orElseArgument;
|
||||
myOrElseCall = orElseCall;
|
||||
myDefaultExpression = defaultExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Replace 'orElse(null)' with 'orElse(" + myDefaultExpression.getText() + ")'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Can be replaced with 'orElse(" + myDefaultExpression.getText() + ")'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement simplify(PsiMethodCallExpression element) {
|
||||
myOrElseArgument.replace(myDefaultExpression);
|
||||
myReturnExpr.replace(myOrElseCall);
|
||||
myReturnVar.delete();
|
||||
return myOrElseCall.getParent();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static CallHandler<CallChainSimplification> handler() {
|
||||
return CallHandler.of(OR_ELSE, call -> {
|
||||
PsiExpression orElseArgument = call.getArgumentList().getExpressions()[0];
|
||||
if (!ExpressionUtils.isNullLiteral(orElseArgument)) return null;
|
||||
PsiLocalVariable returnVar = PsiTreeUtil.getParentOfType(call, PsiLocalVariable.class, true);
|
||||
if(returnVar == null) return null;
|
||||
PsiReturnStatement returnStatement = tryCast(PsiTreeUtil.skipWhitespacesForward(returnVar.getParent()), PsiReturnStatement.class);
|
||||
if (returnStatement == null) return null;
|
||||
PsiExpression returnExpr = returnStatement.getReturnValue();
|
||||
if (returnExpr == null) return null;
|
||||
PsiConditionalExpression ternary = tryCast(PsiUtil.skipParenthesizedExprDown(returnExpr), PsiConditionalExpression.class);
|
||||
if (ternary == null) return null;
|
||||
PsiExpression condition = ternary.getCondition();
|
||||
PsiExpression thenExpr = ternary.getThenExpression();
|
||||
PsiExpression elseExpr = ternary.getElseExpression();
|
||||
if (thenExpr == null || elseExpr == null) return null;
|
||||
PsiVariable nullChecked = ExpressionUtils.getVariableFromNullComparison(condition, true);
|
||||
boolean inverted = false;
|
||||
if (nullChecked == null) {
|
||||
nullChecked = ExpressionUtils.getVariableFromNullComparison(condition, false);
|
||||
if (nullChecked == null) return null;
|
||||
inverted = true;
|
||||
}
|
||||
if (!nullChecked.equals(returnVar) || !ExpressionUtils.isReferenceTo(inverted ? thenExpr : elseExpr, returnVar)) return null;
|
||||
PsiExpression defaultExpression = inverted ? elseExpr : thenExpr;
|
||||
if (VariableAccessUtils.variableIsUsed(returnVar, defaultExpression)) return null;
|
||||
//if(SideEffectChecker.mayHaveSideEffects(defaultExpression)) return null; // new expressions not allowed
|
||||
return new OrElseReturnStreamFix(returnVar, returnExpr, orElseArgument, call, defaultExpression);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// "Replace null check with ifPresent()" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).ifPresent(System.out::println);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Replace 'orElse(null)' with 'orElse(new Person("noname", 0))'" "true"
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -27,6 +27,6 @@ public class Main {
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
return personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).orElse(new Person("noname", 0));
|
||||
return personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).orElseGet(() -> new Person("noname", 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
return personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).orElseGet(() -> new Person("noname", 0));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// "Replace 'orElse(null)' with 'orElse(new Person("noname", 0))'" "true"
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -27,6 +27,6 @@ public class Main {
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
return personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).orElse(new Person("noname", 0));
|
||||
return personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).orElseGet(() -> new Person("noname", 0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Replace null check with orElse("")" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
|
||||
public Person work(Optional<String> o) {
|
||||
return o.orElse("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// "Replace null check with ifPresent()" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
Person maxPerson = personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).<caret>orElse(null);
|
||||
if(maxPerson != null) {
|
||||
System.out.println(maxPerson);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace 'orElse(null)' with 'orElse(new Person("noname", 0))'" "true"
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
Person maxPerson = personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).<caret>orElse(null);
|
||||
if(maxPerson != null) {
|
||||
return maxPerson;
|
||||
} else return new Person("noname", 0);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// "Replace 'orElse(null)' with 'orElse(new Person("noname", 0))'" "true"
|
||||
// "Replace null check with orElseGet(new Person(...))" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// "Replace null check with orElseGet(new Object())" "false"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public Person(Person p) {
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
Person maxPerson = personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).<caret>orElse(null);
|
||||
return maxPerson == null ? new Object() : maxPerson;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// "Replace null check with orElseGet(new Person(...))" "false"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
static class Person {
|
||||
String name;
|
||||
int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public Person(Person p) {
|
||||
}
|
||||
}
|
||||
|
||||
public Person work() {
|
||||
List<Person> personList = Arrays.asList(
|
||||
new Person("Roman", 21),
|
||||
new Person("James", 25),
|
||||
new Person("Kelly", 12)
|
||||
);
|
||||
Person maxPerson = personList.stream().filter(p -> p.getAge() > 13).max(Comparator.comparingInt(Person::getAge)).<caret>orElse(null);
|
||||
return maxPerson == null ? new Person(maxPerson) : maxPerson;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Replace null check with orElse("")" "true"
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class Main {
|
||||
|
||||
public Person work(Optional<String> o) {
|
||||
String v = o.orElse<caret>(null);
|
||||
return v == null? "" : v;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
<li><code>optional.map(x -> Optional.of(x.trim())).orElseGet(Optional::empty)</code> → <code>optional.map(String::trim)</code></li>
|
||||
<li><code>optional.map(x -> (String)x).orElse(null)</code> → <code>(String) optional.orElse(null)</code></li>
|
||||
<li><code>Optional.ofNullable(optional.orElse(null))</code> → <code>optional</code></li>
|
||||
<li><code>val = optional.orElse(null); val != null ? val : defaultExpr </code> → <code>optional.orElse(defaultExpr)</code></li>
|
||||
<li><code>val = optional.orElse(null); if(val != null) expr(val) </code> → <code>optional.ifPresent(val -> expr(val))</code></li>
|
||||
</ul>
|
||||
<!-- tooltip end -->
|
||||
<p><small>New in 2017.2</small></p>
|
||||
|
||||
Reference in New Issue
Block a user