Skip to content

Commit

Permalink
Introduce IsEmpty matcher for use by Refaster templates (#744)
Browse files Browse the repository at this point in the history
While there, generalize a number of Refaster rules using this new matcher.
  • Loading branch information
Stephan202 authored Oct 23, 2023
1 parent 2ff3095 commit fa026de
Show file tree
Hide file tree
Showing 10 changed files with 703 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,40 @@
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;

import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsEmpty;

@OnlineDocumentation
final class AssertJMapRules {
private AssertJMapRules() {}

// XXX: Reduce boilerplate using a `Matcher` that identifies "empty" instances.
static final class AbstractMapAssertIsEmpty<K, V> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
void before(
AbstractMapAssert<?, ?, K, V> mapAssert,
@Matches(IsEmpty.class) Map<? extends K, ? extends V> wellTypedMap,
@Matches(IsEmpty.class) Map<?, ?> arbitrarilyTypedMap,
@Matches(IsEmpty.class) Iterable<? extends K> keys) {
Refaster.anyOf(
mapAssert.containsExactlyEntriesOf(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.isEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.containsOnlyKeys(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
mapAssert.containsExactlyEntriesOf(wellTypedMap),
mapAssert.containsExactlyInAnyOrderEntriesOf(wellTypedMap),
mapAssert.hasSameSizeAs(arbitrarilyTypedMap),
mapAssert.isEqualTo(arbitrarilyTypedMap),
mapAssert.containsOnlyKeys(keys),
mapAssert.containsExactly(),
mapAssert.containsOnly(),
mapAssert.containsOnlyKeys());
Expand Down Expand Up @@ -112,15 +72,9 @@ void after(Map<K, V> map) {

static final class AbstractMapAssertIsNotEmpty<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>()));
AbstractMapAssert<?, ?, K, V> before(
AbstractMapAssert<?, ?, K, V> mapAssert, @Matches(IsEmpty.class) Map<?, ?> map) {
return mapAssert.isNotEqualTo(map);
}

@AfterTemplate
Expand Down Expand Up @@ -148,12 +102,14 @@ MapAssert<K, V> after(Map<K, V> map) {

static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
AbstractMapAssert<?, ?, K, V> before(
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
return mapAssert.isEqualTo(map);
}

@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
AbstractMapAssert<?, ?, K, V> after(
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
return mapAssert.containsExactlyInAnyOrderEntriesOf(map);
}
}
Expand Down Expand Up @@ -187,12 +143,12 @@ MapAssert<K, V> after(Map<K, V> map, int length) {

static final class AbstractMapAssertHasSameSizeAs<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
return mapAssert.hasSize(map.size());
}

@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
return mapAssert.hasSameSizeAs(map);
}
}
Expand Down Expand Up @@ -225,13 +181,14 @@ MapAssert<K, V> after(Map<K, V> map, K key) {

static final class AssertThatMapContainsOnlyKeys<K, V> {
@BeforeTemplate
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(Map<K, V> map, Set<K> keys) {
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(
Map<K, V> map, Set<? extends K> keys) {
return assertThat(map.keySet()).hasSameElementsAs(keys);
}

@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, Set<K> keys) {
MapAssert<K, V> after(Map<K, V> map, Set<? extends K> keys) {
return assertThat(map).containsOnlyKeys(keys);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,23 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multiset;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Stream;
Expand All @@ -47,6 +42,7 @@
import org.assertj.core.api.OptionalLongAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsArray;
import tech.picnic.errorprone.refaster.matchers.IsEmpty;

/** Refaster rules related to AssertJ expressions and statements. */
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
Expand Down Expand Up @@ -181,63 +177,16 @@ OptionalLongAssert after(OptionalLong optional, long expected) {
static final class AssertThatObjectEnumerableIsEmpty<E> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(ObjectEnumerableAssert<?, E> enumAssert) {
void before(
ObjectEnumerableAssert<?, E> enumAssert,
@Matches(IsEmpty.class) Iterable<? extends E> wellTypedIterable,
@Matches(IsEmpty.class) Iterable<?> arbitrarilyTypedIterable) {
Refaster.anyOf(
enumAssert.containsExactlyElementsOf(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
enumAssert.containsExactlyInAnyOrderElementsOf(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
enumAssert.hasSameElementsAs(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
enumAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
enumAssert.isSubsetOf(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
enumAssert.containsExactlyElementsOf(wellTypedIterable),
enumAssert.containsExactlyInAnyOrderElementsOf(wellTypedIterable),
enumAssert.hasSameElementsAs(wellTypedIterable),
enumAssert.hasSameSizeAs(arbitrarilyTypedIterable),
enumAssert.isSubsetOf(wellTypedIterable),
enumAssert.containsExactly(),
enumAssert.containsExactlyInAnyOrder(),
enumAssert.containsOnly(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@

import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
Expand All @@ -26,8 +26,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
Expand All @@ -48,6 +48,7 @@
import tech.picnic.errorprone.refaster.annotation.Description;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.annotation.Severity;
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;

/** Refaster rules related to Reactor expressions and statements. */
Expand Down Expand Up @@ -1555,12 +1556,12 @@ Mono<ImmutableSet<T>> after(Flux<T> flux) {
}

/** Prefer {@link reactor.util.context.Context#empty()}} over more verbose alternatives. */
// XXX: Consider introducing an `IsEmpty` matcher that identifies a wide range of guaranteed-empty
// `Collection` and `Map` expressions.
// XXX: Introduce Refaster rules or a `BugChecker` that maps `(Immutable)Map.of(k, v)` to
// `Context.of(k, v)` and likewise for multi-pair overloads.
static final class ContextEmpty {
@BeforeTemplate
Context before() {
return Context.of(Refaster.anyOf(new HashMap<>(), ImmutableMap.of()));
Context before(@Matches(IsEmpty.class) Map<?, ?> map) {
return Context.of(map);
}

@AfterTemplate
Expand Down Expand Up @@ -1609,13 +1610,13 @@ StepVerifier.FirstStep<? extends T> after(Flux<T> flux) {
}

/** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */
// XXX: Given an `IsEmpty` matcher that identifies a wide range of guaranteed-empty `Iterable`
// expressions, consider also simplifying `step.expectNextSequence(someEmptyIterable)`.
static final class StepVerifierStepIdentity<T> {
@BeforeTemplate
@SuppressWarnings("unchecked")
StepVerifier.Step<T> before(StepVerifier.Step<T> step) {
return Refaster.anyOf(step.expectNext(), step.expectNextCount(0));
StepVerifier.Step<T> before(
StepVerifier.Step<T> step, @Matches(IsEmpty.class) Iterable<? extends T> iterable) {
return Refaster.anyOf(
step.expectNext(), step.expectNextCount(0), step.expectNextSequence(iterable));
}

@AfterTemplate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,19 @@ public ImmutableSet<Object> elidedTypesAndStaticImports() {

void testAbstractMapAssertIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).containsExactlyEntriesOf(ImmutableMap.of());
assertThat(ImmutableMap.of(2, 0)).containsExactlyEntriesOf(ImmutableBiMap.of());
assertThat(ImmutableMap.of(3, 0)).containsExactlyEntriesOf(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(4, 0)).containsExactlyEntriesOf(new HashMap<>());
assertThat(ImmutableMap.of(5, 0)).containsExactlyEntriesOf(new LinkedHashMap<>());
assertThat(ImmutableMap.of(6, 0)).containsExactlyEntriesOf(new TreeMap<>());
assertThat(ImmutableMap.of(7, 0)).hasSameSizeAs(ImmutableMap.of());
assertThat(ImmutableMap.of(8, 0)).hasSameSizeAs(ImmutableBiMap.of());
assertThat(ImmutableMap.of(9, 0)).hasSameSizeAs(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(10, 0)).hasSameSizeAs(new HashMap<>());
assertThat(ImmutableMap.of(11, 0)).hasSameSizeAs(new LinkedHashMap<>());
assertThat(ImmutableMap.of(12, 0)).hasSameSizeAs(new TreeMap<>());
assertThat(ImmutableMap.of(13, 0)).isEqualTo(ImmutableMap.of());
assertThat(ImmutableMap.of(14, 0)).isEqualTo(ImmutableBiMap.of());
assertThat(ImmutableMap.of(15, 0)).isEqualTo(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(16, 0)).isEqualTo(new HashMap<>());
assertThat(ImmutableMap.of(17, 0)).isEqualTo(new LinkedHashMap<>());
assertThat(ImmutableMap.of(18, 0)).isEqualTo(new TreeMap<>());
assertThat(ImmutableMap.of(19, 0)).containsOnlyKeys(ImmutableList.of());
assertThat(ImmutableMap.of(20, 0)).containsOnlyKeys(new ArrayList<>());
assertThat(ImmutableMap.of(21, 0)).containsOnlyKeys(ImmutableSet.of());
assertThat(ImmutableMap.of(22, 0)).containsOnlyKeys(new HashSet<>());
assertThat(ImmutableMap.of(23, 0)).containsOnlyKeys(ImmutableSortedSet.of());
assertThat(ImmutableMap.of(24, 0)).containsOnlyKeys(new TreeSet<>());
assertThat(ImmutableMap.of(25, 0)).containsOnlyKeys(ImmutableMultiset.of());
assertThat(ImmutableMap.of(26, 0)).containsOnlyKeys(ImmutableSortedMultiset.of());
assertThat(ImmutableMap.of(27, 0)).containsExactly();
assertThat(ImmutableMap.of(28, 0)).containsOnly();
assertThat(ImmutableMap.of(29, 0)).containsOnlyKeys();
assertThat(ImmutableMap.of(2, 0)).containsExactlyEntriesOf(ImmutableMap.of(1, 2));
assertThat(ImmutableMap.of(3, 0)).containsExactlyInAnyOrderEntriesOf(ImmutableMap.of());
assertThat(ImmutableMap.of(4, 0))
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2, 3, 4));
assertThat(ImmutableMap.of(5, 0)).hasSameSizeAs(ImmutableMap.of());
assertThat(ImmutableMap.of(6, 0)).hasSameSizeAs(ImmutableMap.of(1, 2));
assertThat(ImmutableMap.of(7, 0)).isEqualTo(ImmutableMap.of());
assertThat(ImmutableMap.of(8, 0)).isEqualTo(ImmutableMap.of("foo", "bar"));
assertThat(ImmutableMap.of(9, 0)).containsOnlyKeys(ImmutableList.of());
assertThat(ImmutableMap.of(10, 0)).containsOnlyKeys(ImmutableList.of(1));
assertThat(ImmutableMap.of(11, 0)).containsExactly();
assertThat(ImmutableMap.of(12, 0)).containsOnly();
assertThat(ImmutableMap.of(13, 0)).containsOnlyKeys();
}

void testAssertThatMapIsEmpty() {
Expand All @@ -84,11 +69,7 @@ void testAssertThatMapIsEmpty() {
ImmutableSet<MapAssert<Integer, Integer>> testAbstractMapAssertIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEqualTo(ImmutableMap.of()),
assertThat(ImmutableMap.of(2, 0)).isNotEqualTo(ImmutableBiMap.of()),
assertThat(ImmutableMap.of(3, 0)).isNotEqualTo(ImmutableSortedMap.of()),
assertThat(ImmutableMap.of(4, 0)).isNotEqualTo(new HashMap<>()),
assertThat(ImmutableMap.of(5, 0)).isNotEqualTo(new LinkedHashMap<>()),
assertThat(ImmutableMap.of(6, 0)).isNotEqualTo(new TreeMap<>()));
assertThat(ImmutableMap.of(2, 0)).isNotEqualTo(ImmutableMap.of("foo", "bar")));
}

ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapIsNotEmpty() {
Expand Down
Loading

0 comments on commit fa026de

Please sign in to comment.