diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index 8720b448599a..19e325454559 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -15,8 +15,10 @@ import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.callMatcher.CallMapper; import com.siyeh.ig.callMatcher.CallMatcher; import com.siyeh.ig.psiutils.ConstructionUtils; +import com.siyeh.ig.psiutils.ExpressionUtils; import com.siyeh.ig.psiutils.MethodUtils; import com.siyeh.ig.psiutils.TypeUtils; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,6 +54,8 @@ public final class HardcodedContracts { instanceCall(JAVA_UTIL_COLLECTION, "addAll", "removeAll", "retainAll").parameterTypes(JAVA_UTIL_COLLECTION), instanceCall(JAVA_UTIL_LIST, "addAll").parameterTypes("int", JAVA_UTIL_COLLECTION), instanceCall(JAVA_UTIL_MAP, "putAll").parameterTypes(JAVA_UTIL_MAP)); + private static final StandardMethodContract NULL_FAIL = new StandardMethodContract(new ValueConstraint[]{NULL_VALUE}, fail()); + private static final StandardMethodContract NOT_NULL_FAIL = new StandardMethodContract(new ValueConstraint[]{NOT_NULL_VALUE}, fail()); /** * @param method method to test @@ -450,48 +454,47 @@ public final class HardcodedContracts { } if (args.length == 1) { PsiType type = args[0].getType(); - return SyntaxTraverser.psiApi().parents(call) + return StreamEx.iterate(call, Objects::nonNull, ExpressionUtils::getCallForQualifier) .skip(1) - .takeWhile(e -> !(e instanceof PsiStatement) && !(e instanceof PsiMember)) - .filter(PsiMethodCallExpression.class) .takeWhile(c -> { String name = c.getMethodExpression().getReferenceName(); return name != null && (name.startsWith("is") || name.equals("describedAs") || name.equals("as")); }) - .filterMap(c -> constraintFromAssertJMatcher(type, c)) + .flatMap(c -> constraintFromAssertJMatcher(type, c)) .toList(); } } return Collections.emptyList(); } - private static @Nullable MethodContract constraintFromAssertJMatcher(PsiType type, PsiMethodCallExpression call) { - if (!call.getArgumentList().isEmpty()) return null; + private static @NotNull StreamEx constraintFromAssertJMatcher(PsiType type, PsiMethodCallExpression call) { + if (!call.getArgumentList().isEmpty()) return StreamEx.empty(); String name = call.getMethodExpression().getReferenceName(); - if (name == null) return null; + if (name == null) return StreamEx.empty(); switch (name) { case "isNotNull": - return new StandardMethodContract(new ValueConstraint[]{NULL_VALUE}, fail()); + return StreamEx.of(NULL_FAIL); case "isNull": - return new StandardMethodContract(new ValueConstraint[]{NOT_NULL_VALUE}, fail()); + return StreamEx.of(NOT_NULL_FAIL); case "isPresent": case "isNotEmpty": - return emptyCheck(type, false); + case "isNotBlank": + return StreamEx.of(NULL_FAIL, emptyCheck(type, false)); case "isNotPresent": case "isEmpty": - return emptyCheck(type, true); + return StreamEx.of(NULL_FAIL, emptyCheck(type, true)); case "isTrue": if (PsiType.BOOLEAN.equals(type) || TypeUtils.typeEquals(JAVA_LANG_BOOLEAN, type)) { - return new StandardMethodContract(new ValueConstraint[]{FALSE_VALUE}, fail()); + return StreamEx.of(new StandardMethodContract(new ValueConstraint[]{FALSE_VALUE}, fail())); } - return null; + return StreamEx.empty(); case "isFalse": if (PsiType.BOOLEAN.equals(type) || TypeUtils.typeEquals(JAVA_LANG_BOOLEAN, type)) { - return new StandardMethodContract(new ValueConstraint[]{TRUE_VALUE}, fail()); + return StreamEx.of(new StandardMethodContract(new ValueConstraint[]{TRUE_VALUE}, fail())); } - return null; + return StreamEx.empty(); } - return null; + return StreamEx.empty(); } private static @Nullable MethodContract emptyCheck(PsiType type, boolean isEmpty) { diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java b/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java index b3bb91ab04a3..a6f077ac4b79 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/AssertThat.java @@ -88,6 +88,16 @@ class Contracts { Assertions.assertThat(id).isPresent().map(this::convert).isEmpty(); if (id.isPresent()) {} } + + void testBlank() { + String string = readString(); + if (string == null) {} + Assertions.assertThat(string).isNotBlank(); + if (string == null) {} + if (string.isEmpty()) {} + } + + native String readString(); native @Nullable String convert(String s); diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java index 4b53d10f6d9e..159bdcdce451 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java @@ -68,6 +68,7 @@ public class HardcodedContractsTest extends DataFlowInspectionTestCase { "public S isNotEmpty() {}" + "public S isEmpty() {}" + "public S isPresent() {}" + + "public S isNotBlank() {}" + "public S map(java.util.function.Function mapper) {}" + "}");