From ecc74e9a2d78a7cbec0b8044ff948111e67cc175 Mon Sep 17 00:00:00 2001 From: "Roman.Ivanov" Date: Wed, 3 Oct 2018 13:06:56 +0700 Subject: [PATCH] BoolUtils: make it recognize methods with negated counterparts: IDEA-199888 --- .../templates/not/smartNegationNoneAny.java | 9 +++ .../not/smartNegationNoneAny_after.java | 9 +++ .../not/smartNegationPresentEmpty.java | 9 +++ .../not/smartNegationPresentEmpty_after.java | 9 +++ .../NotExpressionPostfixTemplateTest.java | 35 ++++++++++ .../src/com/siyeh/ig/psiutils/BoolUtils.java | 70 +++++++++++++++++++ 6 files changed, 141 insertions(+) create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny_after.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty.java create mode 100644 java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty_after.java diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny.java b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny.java new file mode 100644 index 000000000000..fe1b16b21b1c --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny.java @@ -0,0 +1,9 @@ +import java.util.stream.Stream; + +public class Foo { + void m(Stream s) { + if (s.anyMatch(str -> str.equals("foo")).not) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny_after.java new file mode 100644 index 000000000000..1c0f7c0fa4d2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationNoneAny_after.java @@ -0,0 +1,9 @@ +import java.util.stream.Stream; + +public class Foo { + void m(Stream s) { + if (s.noneMatch(str -> str.equals("foo"))) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty.java b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty.java new file mode 100644 index 000000000000..2c79019b4805 --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty.java @@ -0,0 +1,9 @@ +import java.util.Optional; + +public class Foo { + void m() { + if (Optional.of("").isPresent().not) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty_after.java b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty_after.java new file mode 100644 index 000000000000..2ba848ae55ee --- /dev/null +++ b/java/java-tests/testData/codeInsight/template/postfix/templates/not/smartNegationPresentEmpty_after.java @@ -0,0 +1,9 @@ +import java.util.Optional; + +public class Foo { + void m() { + if (Optional.of("").isEmpty()) { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/template/postfix/templates/NotExpressionPostfixTemplateTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/template/postfix/templates/NotExpressionPostfixTemplateTest.java index f1d1eb73483e..6be35c772fb4 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/template/postfix/templates/NotExpressionPostfixTemplateTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/template/postfix/templates/NotExpressionPostfixTemplateTest.java @@ -15,6 +15,7 @@ */ package com.intellij.java.codeInsight.template.postfix.templates; +import com.intellij.testFramework.LightProjectDescriptor; import org.jetbrains.annotations.NotNull; /** @@ -46,5 +47,39 @@ public class NotExpressionPostfixTemplateTest extends PostfixTemplateTestCase { public void testConditionInBooleanMethodCall() { doTest(); } + + public void testSmartNegationPresentEmpty() { + doTest(); + } + + public void testSmartNegationNoneAny() { + doTest(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myFixture.addClass("package java.util;\n" + + "public class Optional {\n" + + " public static Optional of(T value) { return null; }\n" + + " public boolean isPresent() { return true; }\n" + + " public boolean isEmpty() { return false; }\n" + + "}"); + myFixture.addClass("package java.util.stream;\n" + + "\n" + + "import java.util.function.Predicate;\n" + + "\n" + + "public class Stream {\n" + + " boolean anyMatch(Predicate predicate) { return true;}\n" + + " boolean noneMatch(Predicate predicate) { return false;}\n" + + "}"); + } + + @NotNull + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return JAVA_11; + } + // public void testNegation() { doTest(); } // todo: test for chooser } \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java index d7d5288bbef1..30a7092b4ce2 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/BoolUtils.java @@ -17,15 +17,23 @@ package com.siyeh.ig.psiutils; import com.intellij.codeInspection.dataFlow.value.DfaRelationValue; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtil; import com.intellij.util.Function; +import com.siyeh.ig.callMatcher.CallMatcher; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import static com.intellij.psi.CommonClassNames.JAVA_UTIL_OPTIONAL; + public class BoolUtils { private BoolUtils() {} @@ -74,6 +82,64 @@ public class BoolUtils { return getNegatedExpressionText(condition, ParenthesesUtils.NUM_PRECEDENCES, tracker); } + private static final CallMatcher STREAM_ANY_MATCH = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "anyMatch"); + private static final CallMatcher STREAM_NONE_MATCH = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "noneMatch"); + + + 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"; + + 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) + ); + + private static Predicate withMinimalLanguageLevel(CallMatcher matcher, LanguageLevel level) { + return matcher.and(expression -> PsiUtil.getLanguageLevel(expression).isAtLeast(level)); + } + + private static class PredicatedReplacement { + Predicate predicate; + String name; + + private PredicatedReplacement(Predicate predicate, String name) { + this.predicate = predicate; + this.name = name; + } + } + + private static final List ourReplacements = new ArrayList<>(); + static { + ourReplacements.add(new PredicatedReplacement(OPTIONAL_IS_EMPTY, "isPresent")); + ourReplacements.add(new PredicatedReplacement(withMinimalLanguageLevel(OPTIONAL_IS_PRESENT, LanguageLevel.JDK_11), "isEmpty")); + ourReplacements.add(new PredicatedReplacement(STREAM_ANY_MATCH, "noneMatch")); + ourReplacements.add(new PredicatedReplacement(STREAM_NONE_MATCH, "anyMatch")); + } + + private static String findSmartMethodNegation(PsiExpression expression) { + if (!(expression instanceof PsiMethodCallExpression)) return null; + PsiMethodCallExpression call = (PsiMethodCallExpression)expression; + PsiMethodCallExpression copy = (PsiMethodCallExpression)call.copy(); + for (PredicatedReplacement predicatedReplacement : ourReplacements) { + if (predicatedReplacement.predicate.test(call)) { + ExpressionUtils.bindCallTo(copy, predicatedReplacement.name); + return copy.getText(); + } + } + return null; + } + @NotNull public static String getNegatedExpressionText(@Nullable PsiExpression expression, int precedence, @@ -81,6 +147,10 @@ public class BoolUtils { if (expression == null) { return ""; } + if (expression instanceof PsiMethodCallExpression) { + String smartNegation = findSmartMethodNegation(expression); + if (smartNegation != null) return smartNegation; + } if (expression instanceof PsiParenthesizedExpression) { final PsiParenthesizedExpression parenthesizedExpression = (PsiParenthesizedExpression)expression; PsiExpression operand = parenthesizedExpression.getExpression();