diff --git a/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java index ba8ab217f64d..2a1be29ca651 100644 --- a/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/SimplifyOptionalCallChainsInspection.java @@ -6,6 +6,7 @@ import com.intellij.codeInspection.dataFlow.DfaFactType; import com.intellij.codeInspection.util.LambdaGenerationUtil; import com.intellij.codeInspection.util.OptionalUtil; import com.intellij.openapi.project.Project; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.PsiDiamondTypeUtil; import com.intellij.psi.search.searches.ReferencesSearch; @@ -23,31 +24,48 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.regex.Pattern; +import static com.intellij.codeInspection.util.OptionalUtil.*; +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_OPTIONAL; import static com.intellij.util.ObjectUtils.tryCast; public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalInspectionTool { private static final CallMatcher OPTIONAL_OR_ELSE = - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1); + CallMatcher.instanceCall(JAVA_UTIL_OPTIONAL, "orElse").parameterCount(1); private static final CallMatcher OPTIONAL_GET = - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "get").parameterCount(0); + CallMatcher.instanceCall(JAVA_UTIL_OPTIONAL, "get").parameterCount(0); private static final CallMatcher OPTIONAL_OR_ELSE_GET = - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "orElseGet").parameterCount(1); + CallMatcher.instanceCall(JAVA_UTIL_OPTIONAL, "orElseGet").parameterCount(1); private static final CallMatcher OPTIONAL_MAP = - CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "map").parameterCount(1); + CallMatcher.instanceCall(JAVA_UTIL_OPTIONAL, "map").parameterCount(1); private static final CallMatcher OPTIONAL_OF_NULLABLE = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable").parameterCount(1); + CallMatcher.staticCall(JAVA_UTIL_OPTIONAL, "ofNullable").parameterCount(1); private static final CallMatcher OPTIONAL_OF_OF_NULLABLE = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "ofNullable", "of").parameterCount(1); + CallMatcher.staticCall(JAVA_UTIL_OPTIONAL, "ofNullable", "of").parameterCount(1); + private static final CallMatcher OPTIONAL_IS_PRESENT = + CallMatcher.anyOf( + CallMatcher.exactInstanceCall(JAVA_UTIL_OPTIONAL, "isPresent").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_INT, "isPresent").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_LONG, "isPresent").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_DOUBLE, "isPresent").parameterCount(0) + ); + private static final CallMatcher OPTIONAL_IS_EMPTY = + CallMatcher.anyOf( + CallMatcher.exactInstanceCall(JAVA_UTIL_OPTIONAL, "isEmpty").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_INT, "isEmpty").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_LONG, "isEmpty").parameterCount(0), + CallMatcher.exactInstanceCall(OPTIONAL_DOUBLE, "isEmpty").parameterCount(0) + ); @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + LanguageLevel level = PsiUtil.getLanguageLevel(holder.getFile()); + if (level.isLessThan(LanguageLevel.JDK_1_8)) { return PsiElementVisitor.EMPTY_VISITOR; } - return new OptionalChainVisitor() { + return new OptionalChainVisitor(level) { @Override protected void handleSimplification(@NotNull PsiElement element, @NotNull OptionalChainSimplification simplification) { @@ -57,12 +75,19 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI } private static abstract class OptionalChainVisitor extends JavaElementVisitor { + private final LanguageLevel myLevel; + + private OptionalChainVisitor(LanguageLevel level) { + myLevel = level; + } + @Override public void visitMethodCallExpression(PsiMethodCallExpression call) { if (OPTIONAL_GET.test(call)) { handleRewrapping(call, OPTIONAL_OF_OF_NULLABLE); return; } + handleInvertedPresentOrEmpty(call); PsiExpression falseArg = null; boolean useOrElseGet = false; if (OPTIONAL_OR_ELSE.test(call)) { @@ -83,6 +108,19 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI handleOrElseNullConditionalAction(call, falseArg); } + private void handleInvertedPresentOrEmpty(PsiMethodCallExpression call) { + if (myLevel.isLessThan(LanguageLevel.JDK_11)) return; + if (!BoolUtils.isNegated(call)) return; + PsiElement nameElement = call.getMethodExpression().getReferenceNameElement(); + if (nameElement == null) return; + if (OPTIONAL_IS_EMPTY.test(call)) { + handleSimplification(nameElement, new FlipEmptyPresentFix("isPresent")); + } + else if (OPTIONAL_IS_PRESENT.test(call)) { + handleSimplification(nameElement, new FlipEmptyPresentFix("isEmpty")); + } + } + private void handleRewrapping(PsiMethodCallExpression call, CallMatcher wrapper) { PsiElement parent = PsiUtil.skipParenthesizedExprUp(call.getParent()); if (!(parent instanceof PsiExpressionList)) return; @@ -471,8 +509,6 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI PsiTreeUtil.isAncestor(nextStatement, reference.getElement(), false))) return null; return new Context(lambdaExpr, nextStatement, statement, returnVar, orElseCall); } - - } @@ -497,6 +533,35 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI } } + private static class FlipEmptyPresentFix implements OptionalChainSimplification { + private final String myReplacement; + + private FlipEmptyPresentFix(String replacement) { + myReplacement = replacement; + } + + @NotNull + @Override + public String getName() { + return CommonQuickFixBundle.message("fix.replace.with.x", myReplacement + "()"); + } + + @NotNull + @Override + public String getDescription() { + return "'" + myReplacement + "()' can be used instead"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull PsiElement element) { + PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class); + if (call == null) return; + PsiPrefixExpression negation = tryCast(PsiUtil.skipParenthesizedExprUp(call.getParent()), PsiPrefixExpression.class); + if (negation == null || BoolUtils.getNegated(negation) != call) return; + ExpressionUtils.bindCallTo(call, myReplacement); + new CommentTracker().replaceAndRestoreComments(negation, call); + } + } private static class SimplifyOptionalChainFix implements OptionalChainSimplification { private final String myReplacement; @@ -531,4 +596,4 @@ public class SimplifyOptionalCallChainsInspection extends AbstractBaseJavaLocalI PsiDiamondTypeUtil.removeRedundantTypeArguments(result); } } -} +} \ No newline at end of file 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 5c07df7115db..c1cab39015e3 100644 --- a/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java +++ b/java/java-impl/src/com/intellij/codeInspection/util/OptionalUtil.java @@ -18,9 +18,9 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; public class OptionalUtil { - private static final String OPTIONAL_INT = "java.util.OptionalInt"; - private static final String OPTIONAL_LONG = "java.util.OptionalLong"; - private static final String OPTIONAL_DOUBLE = "java.util.OptionalDouble"; + public static final String OPTIONAL_INT = "java.util.OptionalInt"; + public static final String OPTIONAL_LONG = "java.util.OptionalLong"; + public static final String OPTIONAL_DOUBLE = "java.util.OptionalDouble"; private static final CallMatcher OPTIONAL_OF = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "of", "ofNullable").parameterCount(1); diff --git a/java/java-tests/testData/inspection/optionalChains/afterIsPresentJava11.java b/java/java-tests/testData/inspection/optionalChains/afterIsPresentJava11.java new file mode 100644 index 000000000000..45e19c41deb5 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/afterIsPresentJava11.java @@ -0,0 +1,8 @@ +// "Replace with 'isEmpty()'" "true" +import java.util.Optional; + +public class Test { + void test(Optional opt) { + if(!opt.isPresent()) return; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava11.java b/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava11.java new file mode 100644 index 000000000000..45e19c41deb5 --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava11.java @@ -0,0 +1,8 @@ +// "Replace with 'isEmpty()'" "true" +import java.util.Optional; + +public class Test { + void test(Optional opt) { + if(!opt.isPresent()) return; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava9.java b/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava9.java new file mode 100644 index 000000000000..cf253c247f9f --- /dev/null +++ b/java/java-tests/testData/inspection/optionalChains/beforeIsPresentJava9.java @@ -0,0 +1,8 @@ +// "Replace with 'isEmpty()'" "false" +import java.util.Optional; + +public class Test { + void test(Optional opt) { + if(!opt.isPresent()) return; + } +} \ No newline at end of file