BoolUtils: make it recognize methods with negated counterparts: IDEA-199888

This commit is contained in:
Roman.Ivanov
2018-10-04 17:39:11 +07:00
parent c4b7de202d
commit ecc74e9a2d
6 changed files with 141 additions and 0 deletions
@@ -0,0 +1,9 @@
import java.util.stream.Stream;
public class Foo {
void m(Stream<String> s) {
if (s.anyMatch(str -> str.equals("foo")).not<caret>) {
}
}
}
@@ -0,0 +1,9 @@
import java.util.stream.Stream;
public class Foo {
void m(Stream<String> s) {
if (s.noneMatch(str -> str.equals("foo"))<caret>) {
}
}
}
@@ -0,0 +1,9 @@
import java.util.Optional;
public class Foo {
void m() {
if (Optional.of("").isPresent().not<caret>) {
}
}
}
@@ -0,0 +1,9 @@
import java.util.Optional;
public class Foo {
void m() {
if (Optional.of("").isEmpty()<caret>) {
}
}
}
@@ -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<T> {\n" +
" public static <T> Optional<T> 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<T> {\n" +
" boolean anyMatch(Predicate<? super T> predicate) { return true;}\n" +
" boolean noneMatch(Predicate<? super T> predicate) { return false;}\n" +
"}");
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return JAVA_11;
}
// public void testNegation() { doTest(); } // todo: test for chooser
}
@@ -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<PsiMethodCallExpression> withMinimalLanguageLevel(CallMatcher matcher, LanguageLevel level) {
return matcher.and(expression -> PsiUtil.getLanguageLevel(expression).isAtLeast(level));
}
private static class PredicatedReplacement {
Predicate<PsiMethodCallExpression> predicate;
String name;
private PredicatedReplacement(Predicate<PsiMethodCallExpression> predicate, String name) {
this.predicate = predicate;
this.name = name;
}
}
private static final List<PredicatedReplacement> 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();