diff --git a/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java new file mode 100644 index 000000000000..9ab4436e9e22 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java @@ -0,0 +1,157 @@ +/* + * 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; + +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.util.PsiExpressionTrimRenderer; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.util.LambdaRefactoringUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.MethodCallUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * @author Tagir Valeev + */ +public class SimplifyOptionalCallChainsInspection extends BaseJavaBatchLocalInspectionTool { + private static final CallMatcher OPTIONAL_OR_ELSE = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1); + private static final CallMatcher OPTIONAL_OR_ELSE_GET = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElseGet").parameterCount(1); + private static final CallMatcher OPTIONAL_MAP = + CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "map").parameterCount(1); + + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new 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; + 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)); + } + holder.registerProblem(Objects.requireNonNull(call.getMethodExpression().getReferenceNameElement()), + "Optional chain can be simplified", + new SimplifyOptionalChainFix(proposed, displayCode)); + } + } + }; + } + + private static PsiLambdaExpression getLambda(PsiExpression initializer) { + PsiExpression expression = PsiUtil.skipParenthesizedExprDown(initializer); + if (expression instanceof PsiLambdaExpression) { + return (PsiLambdaExpression)expression; + } + if (expression instanceof PsiMethodReferenceExpression) { + return LambdaRefactoringUtil.createLambda((PsiMethodReferenceExpression)expression, true); + } + return null; + } + + private static class SimplifyOptionalChainFix implements LocalQuickFix { + private final String myReplacement; + private final String myDisplayCode; + + public SimplifyOptionalChainFix(String replacement, String displayCode) { + myReplacement = replacement; + myDisplayCode = displayCode; + } + + @Nls + @NotNull + @Override + public String getName() { + return myDisplayCode.isEmpty() ? "Remove redundant steps from optional chain" : + "Simplify optional chain to '"+myDisplayCode+"'"; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return "Simplify optional call chain"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(descriptor.getStartElement(), PsiMethodCallExpression.class); + if (call == null) return; + PsiExpression replacementExpression = JavaPsiFacade.getElementFactory(project).createExpressionFromText(myReplacement, call); + PsiElement result = call.replace(replacementExpression); + LambdaCanBeMethodReferenceInspection.replaceAllLambdasWithMethodReferences(result); + PsiDiamondTypeUtil.removeRedundantTypeArguments(result); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java b/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java index ba613719b7a0..7d13299358a7 100644 --- a/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java +++ b/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java @@ -17,9 +17,12 @@ package com.intellij.codeInspection.util; import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.psi.*; +import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.util.RefactoringUtil; +import com.siyeh.ig.callMatcher.CallMatcher; +import com.siyeh.ig.psiutils.BoolUtils; import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodCallUtils; import org.jetbrains.annotations.Contract; @@ -33,6 +36,9 @@ public class OptionalUtil { private static final String OPTIONAL_LONG = "java.util.OptionalLong"; private static final String OPTIONAL_DOUBLE = "java.util.OptionalDouble"; + private static final CallMatcher OPTIONAL_OF = + CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "of", "ofNullable").parameterCount(1); + @NotNull @Contract(pure = true) public static String getOptionalClass(String type) { @@ -102,37 +108,59 @@ public class OptionalUtil { PsiExpression trueExpression, PsiExpression falseExpression, PsiType targetType, boolean useOrElseGet) { if (!ExpressionUtils.isReferenceTo(trueExpression, var)) { - if(trueExpression instanceof PsiTypeCastExpression && ExpressionUtils.isNullLiteral(falseExpression)) { + if (trueExpression instanceof PsiTypeCastExpression && ExpressionUtils.isNullLiteral(falseExpression)) { PsiTypeCastExpression castExpression = (PsiTypeCastExpression)trueExpression; PsiTypeElement castType = castExpression.getCastType(); // pull cast outside to avoid the .map() step - if(castType != null && ExpressionUtils.isReferenceTo(castExpression.getOperand(), var)) { + if (castType != null && ExpressionUtils.isReferenceTo(castExpression.getOperand(), var)) { return "(" + castType.getText() + ")" + qualifier + ".orElse(null)"; } } - if(ExpressionUtils.isLiteral(falseExpression, Boolean.FALSE) && PsiType.BOOLEAN.equals(trueExpression.getType())) { + if (ExpressionUtils.isLiteral(falseExpression, Boolean.FALSE) && PsiType.BOOLEAN.equals(trueExpression.getType())) { + if (ExpressionUtils.isLiteral(trueExpression, Boolean.TRUE)) { + return qualifier + ".isPresent()"; + } return qualifier + ".filter(" + LambdaUtil.createLambda(var, trueExpression) + ").isPresent()"; } - if(trueExpression instanceof PsiConditionalExpression) { + if (ExpressionUtils.isLiteral(falseExpression, Boolean.TRUE) && ExpressionUtils.isLiteral(trueExpression, Boolean.FALSE)) { + return "!" + qualifier + ".isPresent()"; + } + if (trueExpression instanceof PsiConditionalExpression) { PsiConditionalExpression condition = (PsiConditionalExpression)trueExpression; + PsiExpression thenExpression = condition.getThenExpression(); PsiExpression elseExpression = condition.getElseExpression(); - if(elseExpression != null && PsiEquivalenceUtil.areElementsEquivalent(falseExpression, elseExpression)) { + if (elseExpression != null && PsiEquivalenceUtil.areElementsEquivalent(falseExpression, elseExpression)) { return generateOptionalUnwrap( qualifier + ".filter(" + LambdaUtil.createLambda(var, condition.getCondition()) + ")", var, condition.getThenExpression(), falseExpression, targetType, useOrElseGet); } + if (thenExpression != null && PsiEquivalenceUtil.areElementsEquivalent(falseExpression, thenExpression)) { + return generateOptionalUnwrap( + qualifier + ".filter(" + var.getName() + " -> " + BoolUtils.getNegatedExpressionText(condition.getCondition()) + ")", var, + condition.getElseExpression(), falseExpression, targetType, useOrElseGet); + } } - if(isOptionalEmptyCall(falseExpression)) { + String suffix = null; + if (isOptionalEmptyCall(falseExpression)) { + suffix = ""; + } + else if (PsiUtil.isLanguageLevel9OrHigher(trueExpression) && + InheritanceUtil.isInheritor(falseExpression.getType(), CommonClassNames.JAVA_UTIL_OPTIONAL) && + LambdaGenerationUtil.canBeUncheckedLambda(falseExpression)) { + suffix = ".or(() -> " + falseExpression.getText() + ")"; + } + if (suffix != null) { // simplify "qualifier.map(x -> Optional.of(x)).orElse(Optional.empty())" to "qualifier" - if (trueExpression instanceof PsiMethodCallExpression && - MethodCallUtils.isCallToStaticMethod((PsiMethodCallExpression)trueExpression, CommonClassNames.JAVA_UTIL_OPTIONAL, "of", 1)) { + if (trueExpression instanceof PsiMethodCallExpression && OPTIONAL_OF.test((PsiMethodCallExpression)trueExpression)) { PsiExpression arg = ((PsiMethodCallExpression)trueExpression).getArgumentList().getExpressions()[0]; - if(ExpressionUtils.isReferenceTo(arg, var)) { - return qualifier; + if (ExpressionUtils.isReferenceTo(arg, var)) { + return qualifier + suffix; } - return qualifier + ".map(" + LambdaUtil.createLambda(var, arg) + ")"; + return qualifier + ".map(" + LambdaUtil.createLambda(var, arg) + ")" + suffix; + } + if (suffix.isEmpty()) { + return qualifier + ".flatMap(" + LambdaUtil.createLambda(var, trueExpression) + ")"; } - return qualifier + ".flatMap(" + LambdaUtil.createLambda(var, trueExpression) + ")"; } trueExpression = targetType == null ? trueExpression : RefactoringUtil.convertInitializerToNormalExpression(trueExpression, targetType); diff --git a/java/java-tests/testData/inspection/optionalChains/afterCast.java b/java/java-tests/testData/inspection/optionalChains/afterCast.java new file mode 100644 index 000000000000..dcc5195bf5d3 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterCast.java @@ -0,0 +1,8 @@ +// "Simplify optional chain to '(String)...orElse(...)'" "true" +import java.util.Optional; + +public class Test { + public void test(Optional opt) { + String result = (String) opt.filter(opt -> opt instanceof String).orElse(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterConditional.java b/java/java-tests/testData/inspection/optionalChains/afterConditional.java new file mode 100644 index 000000000000..a260435e6f2e --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterConditional.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to 'opt.filter(...).map(...).orElseGet(...)'" "true" +import java.util.Optional; + +public class Test { + public String getDefaultValue() { + return "foo"; + } + + public void test(Optional opt) { + String result = opt.filter(obj -> !obj.isEmpty()).map(String::trim).orElseGet(this::getDefaultValue); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterFalseTrue.java b/java/java-tests/testData/inspection/optionalChains/afterFalseTrue.java new file mode 100644 index 000000000000..ede1933e072b --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterFalseTrue.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to '!...isPresent()'" "true" +import java.util.Optional; + +public class Test { + interface Dto { + int getId(); + } + + public void test(Dto dto) { + boolean present = !Optional.ofNullable(dto).map(Dto::getId).isPresent(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty.java b/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty.java new file mode 100644 index 000000000000..8babd43357b6 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty.java @@ -0,0 +1,12 @@ +// "Remove redundant steps from optional chain" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt(); + } + + private Optional getOpt() { + return Optional.of("foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty2.java b/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty2.java new file mode 100644 index 000000000000..00ddf97483eb --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterOptionalEmpty2.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to 'getOpt().map(...)'" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt().map(String::trim); + } + + private Optional getOpt() { + return Optional.of("foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterOrJava9.java b/java/java-tests/testData/inspection/optionalChains/afterOrJava9.java new file mode 100644 index 000000000000..c3e1fdba38f2 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterOrJava9.java @@ -0,0 +1,16 @@ +// "Simplify optional chain to 'getOpt().or(...)'" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt().or(this::getOpt2); + } + + private Optional getOpt() { + return Optional.of("foo"); + } + + private Optional getOpt2() { + return Optional.of("bar"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/afterTrueFalse.java b/java/java-tests/testData/inspection/optionalChains/afterTrueFalse.java new file mode 100644 index 000000000000..6cec3e0c552e --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterTrueFalse.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to '...isPresent()'" "true" +import java.util.Optional; + +public class Test { + interface Dto { + int getId(); + } + + public void test(Dto dto) { + boolean present = Optional.ofNullable(dto).map(Dto::getId).isPresent(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeCast.java b/java/java-tests/testData/inspection/optionalChains/beforeCast.java new file mode 100644 index 000000000000..4817bac660b7 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeCast.java @@ -0,0 +1,8 @@ +// "Simplify optional chain to '(String)...orElse(...)'" "true" +import java.util.Optional; + +public class Test { + public void test(Optional opt) { + String result = opt.filter(opt -> opt instanceof String).map(opt -> (String) opt).orElse(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeConditional.java b/java/java-tests/testData/inspection/optionalChains/beforeConditional.java new file mode 100644 index 000000000000..e1c9d66f2795 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeConditional.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to 'opt.filter(...).map(...).orElseGet(...)'" "true" +import java.util.Optional; + +public class Test { + public String getDefaultValue() { + return "foo"; + } + + public void test(Optional opt) { + String result = opt.map(obj -> obj.isEmpty() ? getDefaultValue() : obj.trim()).orElseGet(this::getDefaultValue); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeConditionalShort.java b/java/java-tests/testData/inspection/optionalChains/beforeConditionalShort.java new file mode 100644 index 000000000000..87f01fa31747 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeConditionalShort.java @@ -0,0 +1,14 @@ +// "Fix all 'Simplify Optional call chains' problems in file" "false" +import java.util.Optional; + +public class Test { + public String getDefaultValue() { + return "foo"; + } + + public void test(Optional opt) { + // proposed change is longer: + // opt.filter(obj -> !obj.isEmpty()).map(obj -> obj.trim()).orElse(null); + String result = opt.map(obj -> obj.isEmpty() ? null : obj.trim()).orElse(null); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeFalseTrue.java b/java/java-tests/testData/inspection/optionalChains/beforeFalseTrue.java new file mode 100644 index 000000000000..a275582143a1 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeFalseTrue.java @@ -0,0 +1,14 @@ +// "Simplify optional chain to '!...isPresent()'" "true" +import java.util.Optional; + +public class Test { + interface Dto { + int getId(); + } + + public void test(Dto dto) { + boolean present = Optional.ofNullable(dto).map(Dto::getId).map(obj -> { + return false; + }).orElseGet(() -> true); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty.java b/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty.java new file mode 100644 index 000000000000..8bf30a5f72d8 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty.java @@ -0,0 +1,12 @@ +// "Remove redundant steps from optional chain" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt().map(Optional::of).orElse(Optional.empty()); + } + + private Optional getOpt() { + return Optional.of("foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty2.java b/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty2.java new file mode 100644 index 000000000000..bc213bf7a84b --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeOptionalEmpty2.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to 'getOpt().map(...)'" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt().map(x -> Optional.ofNullable(x.trim())).orElseGet(Optional::empty); + } + + private Optional getOpt() { + return Optional.of("foo"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeOrJava8.java b/java/java-tests/testData/inspection/optionalChains/beforeOrJava8.java new file mode 100644 index 000000000000..1192fd49dbd4 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeOrJava8.java @@ -0,0 +1,17 @@ +// "Simplify optional chain to 'getOpt().or(...)'" "false" +import java.util.Optional; + +public class Test { + public Optional test() { + // no simpler alternative in Java 8 + return getOpt().map(Optional::of).orElseGet(this::getOpt2); + } + + private Optional getOpt() { + return Optional.of("foo"); + } + + private Optional getOpt2() { + return Optional.of("bar"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeOrJava9.java b/java/java-tests/testData/inspection/optionalChains/beforeOrJava9.java new file mode 100644 index 000000000000..925649d3a013 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeOrJava9.java @@ -0,0 +1,16 @@ +// "Simplify optional chain to 'getOpt().or(...)'" "true" +import java.util.Optional; + +public class Test { + public Optional test() { + return getOpt().map(Optional::of).orElseGet(this::getOpt2); + } + + private Optional getOpt() { + return Optional.of("foo"); + } + + private Optional getOpt2() { + return Optional.of("bar"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeTrueFalse.java b/java/java-tests/testData/inspection/optionalChains/beforeTrueFalse.java new file mode 100644 index 000000000000..432c7dff7fde --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeTrueFalse.java @@ -0,0 +1,12 @@ +// "Simplify optional chain to '...isPresent()'" "true" +import java.util.Optional; + +public class Test { + interface Dto { + int getId(); + } + + public void test(Dto dto) { + boolean present = Optional.ofNullable(dto).map(Dto::getId).map(obj -> true).orElse(false); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/SimplifyOptionalCallChainsInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/SimplifyOptionalCallChainsInspectionTest.java new file mode 100644 index 000000000000..91e614dedcea --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/SimplifyOptionalCallChainsInspectionTest.java @@ -0,0 +1,52 @@ +/* + * 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; + +import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import org.jetbrains.annotations.NotNull; + +/** + * @author Tagir Valeev + */ +public class SimplifyOptionalCallChainsInspectionTest extends LightQuickFixParameterizedTestCase { + @Override + protected LanguageLevel getLanguageLevel() { + return getTestName(false).endsWith("Java9.java") ? LanguageLevel.JDK_1_9 : LanguageLevel.JDK_1_8; + } + + @Override + protected Sdk getProjectJDK() { + return getTestName(false).endsWith("Java9.java") ? IdeaTestUtil.getMockJdk9() : IdeaTestUtil.getMockJdk18(); + } + + @NotNull + @Override + protected LocalInspectionTool[] configureLocalInspectionTools() { + return new LocalInspectionTool[]{new SimplifyOptionalCallChainsInspection()}; + } + + public void test() throws Exception { + doAllTests(); + } + + @Override + protected String getBasePath() { + return "/inspection/optionalChains"; + } +} diff --git a/resources-en/src/inspectionDescriptions/SimplifyOptionalCallChains.html b/resources-en/src/inspectionDescriptions/SimplifyOptionalCallChains.html new file mode 100644 index 000000000000..bbdc1308cbc9 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/SimplifyOptionalCallChains.html @@ -0,0 +1,12 @@ + + +

This inspection warns about Optional call chains which could be simplified. Here are some examples of possible simplifications:

+
    +
  • optional.map(x -> true).orElse(false)optional.isPresent()
  • +
  • optional.map(x -> Optional.of(x.trim())).orElseGet(Optional::empty)optional.map(String::trim)
  • +
  • optional.map(x -> (String)x).orElse(null)(String) optional.orElse(null)
  • +
+ +

New in 2017.2

+ + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 464a8311e3cb..8975072427de 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -889,6 +889,11 @@ groupKey="group.names.declaration.redundancy" enabledByDefault="true" level="WARNING" implementationClass="com.intellij.codeInspection.SimplifyStreamApiCallChainsInspection" displayName="Simplify stream API call chains"/> +