All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.picnic.errorprone.refasterrules.OptionalRules Maven / Gradle / Ivy

There is a newer version: 0.19.1
Show newest version
package tech.picnic.errorprone.refasterrules;

import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;

import com.google.common.collect.Streams;
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.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;

/** Refaster rules related to expressions dealing with {@link Optional}s. */
@OnlineDocumentation
final class OptionalRules {
  private OptionalRules() {}

  /** Prefer {@link Optional#empty()} over the more contrived alternative. */
  static final class OptionalEmpty {
    @BeforeTemplate
    Optional before() {
      return Optional.ofNullable(null);
    }

    @AfterTemplate
    Optional after() {
      return Optional.empty();
    }
  }

  static final class OptionalOfNullable {
    // XXX: Refaster should be smart enough to also rewrite occurrences in which there are
    // parentheses around the null check, but that's currently not the case. Try to fix that.
    @BeforeTemplate
    @SuppressWarnings("TernaryOperatorOptionalNegativeFiltering" /* Special case. */)
    Optional before(@Nullable T object) {
      return object == null ? Optional.empty() : Optional.of(object);
    }

    @AfterTemplate
    Optional after(T object) {
      return Optional.ofNullable(object);
    }
  }

  /** Prefer {@link Optional#isEmpty()} over the more verbose alternative. */
  static final class OptionalIsEmpty {
    @BeforeTemplate
    boolean before(Optional optional) {
      return !optional.isPresent();
    }

    @AfterTemplate
    boolean after(Optional optional) {
      return optional.isEmpty();
    }
  }

  /** Prefer {@link Optional#isPresent()} over the inverted alternative. */
  static final class OptionalIsPresent {
    @BeforeTemplate
    boolean before(Optional optional) {
      return !optional.isEmpty();
    }

    @AfterTemplate
    boolean after(Optional optional) {
      return optional.isPresent();
    }
  }

  /** Prefer {@link Optional#orElseThrow()} over the less explicit {@link Optional#get()}. */
  static final class OptionalOrElseThrow {
    @BeforeTemplate
    @SuppressWarnings({
      "java:S3655" /* Matched expressions are in practice embedded in a larger context. */,
      "NullAway"
    })
    T before(Optional optional) {
      return optional.get();
    }

    @AfterTemplate
    T after(Optional optional) {
      return optional.orElseThrow();
    }
  }

  /** Prefer {@link Optional#orElseThrow()} over the less explicit {@link Optional#get()}. */
  // XXX: This rule is analogous to `OptionalOrElseThrow` above. Arguably this is its
  // generalization. If/when Refaster is extended to understand this, delete the rule above.
  static final class OptionalOrElseThrowMethodReference {
    @BeforeTemplate
    Function, T> before() {
      return Optional::get;
    }

    @AfterTemplate
    Function, T> after() {
      return Optional::orElseThrow;
    }
  }

  /** Prefer {@link Optional#equals(Object)} over more contrived alternatives. */
  static final class OptionalEqualsOptional {
    @BeforeTemplate
    boolean before(Optional optional, S value) {
      return Refaster.anyOf(
          optional.filter(value::equals).isPresent(), optional.stream().anyMatch(value::equals));
    }

    @AfterTemplate
    boolean after(Optional optional, S value) {
      return optional.equals(Optional.of(value));
    }
  }

  /**
   * Don't use the ternary operator to extract the first element of a possibly-empty {@link
   * Iterator} as an {@link Optional}.
   */
  static final class OptionalFirstIteratorElement {
    @BeforeTemplate
    Optional before(Iterator it) {
      return it.hasNext() ? Optional.of(it.next()) : Optional.empty();
    }

    @AfterTemplate
    @UseImportPolicy(STATIC_IMPORT_ALWAYS)
    Optional after(Iterator it) {
      return Streams.stream(it).findFirst();
    }
  }

  /** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
  // XXX: This rule may introduce a compilation error: the `test` expression may reference a
  // non-effectively final variable, which is not allowed in the replacement lambda expression.
  // Review whether a `@Matcher` can be used to avoid this.
  abstract static class TernaryOperatorOptionalPositiveFiltering {
    @Placeholder
    abstract boolean test(T value);

    @BeforeTemplate
    Optional before(T input) {
      return test(input) ? Optional.of(input) : Optional.empty();
    }

    @AfterTemplate
    Optional after(T input) {
      return Refaster.emitCommentBefore(
          "Or Optional.ofNullable (can't auto-infer).", Optional.of(input).filter(v -> test(v)));
    }
  }

  /** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
  // XXX: This rule may introduce a compilation error: the `test` expression may reference a
  // non-effectively final variable, which is not allowed in the replacement lambda expression.
  // Review whether a `@Matcher` can be used to avoid this.
  abstract static class TernaryOperatorOptionalNegativeFiltering {
    @Placeholder
    abstract boolean test(T value);

    @BeforeTemplate
    Optional before(T input) {
      return test(input) ? Optional.empty() : Optional.of(input);
    }

    @AfterTemplate
    Optional after(T input) {
      return Refaster.emitCommentBefore(
          "Or Optional.ofNullable (can't auto-infer).", Optional.of(input).filter(v -> !test(v)));
    }
  }

  /**
   * Prefer {@link Optional#filter(Predicate)} over {@link Optional#map(Function)} when converting
   * an {@link Optional} to a boolean.
   */
  static final class MapOptionalToBoolean {
    @BeforeTemplate
    @SuppressWarnings("OptionalOrElseGet" /* Rule is confused by `Refaster#anyOf` usage. */)
    boolean before(Optional optional, Function predicate) {
      return optional.map(predicate).orElse(Refaster.anyOf(false, Boolean.FALSE));
    }

    @AfterTemplate
    boolean after(Optional optional, Predicate predicate) {
      return optional.filter(predicate).isPresent();
    }
  }

  /**
   * Prefer {@link Optional#map} over a {@link Optional#flatMap} that wraps the result of a
   * transformation in an {@link Optional}; the former operation transforms {@code null} to {@link
   * Optional#empty()}.
   */
  abstract static class MapToNullable {
    @Placeholder
    abstract S toNullableFunction(@MayOptionallyUse T element);

    @BeforeTemplate
    Optional before(Optional optional) {
      return optional.flatMap(
          v ->
              Refaster.anyOf(
                  Optional.of(toNullableFunction(v)), Optional.ofNullable(toNullableFunction(v))));
    }

    @AfterTemplate
    Optional after(Optional optional) {
      return optional.map(v -> toNullableFunction(v));
    }
  }

  abstract static class FlatMapToOptional {
    @Placeholder
    abstract Optional toOptionalFunction(@MayOptionallyUse T element);

    @BeforeTemplate
    @SuppressWarnings("NullAway")
    Optional before(Optional optional) {
      return optional.map(v -> toOptionalFunction(v).orElseThrow());
    }

    @AfterTemplate
    Optional after(Optional optional) {
      return optional.flatMap(v -> toOptionalFunction(v));
    }
  }

  static final class OrOrElseThrow {
    @BeforeTemplate
    @SuppressWarnings("NullAway")
    T before(Optional o1, Optional o2) {
      return o1.orElseGet(() -> o2.orElseThrow());
    }

    @AfterTemplate
    @SuppressWarnings("NullAway")
    T after(Optional o1, Optional o2) {
      return o1.or(() -> o2).orElseThrow();
    }
  }

  /**
   * Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the
   * fallback value is not the result of a trivial computation.
   */
  // XXX: This rule may introduce a compilation error: the `value` expression may reference a
  // non-effectively final variable, which is not allowed in the replacement lambda expression.
  // Review whether a `@Matcher` can be used to avoid this.
  // XXX: Once `MethodReferenceUsage` is "production ready", replace
  // `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and
  // reimplement the matcher accordingly).
  static final class OptionalOrElseGet {
    @BeforeTemplate
    T before(Optional optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
      return optional.orElse(value);
    }

    @AfterTemplate
    T after(Optional optional, T value) {
      return optional.orElseGet(() -> value);
    }
  }

  /**
   * Flatten a stream of {@link Optional}s using {@link Optional#stream()}, rather than using one of
   * the more verbose alternatives.
   */
  // XXX: Do we need the `.filter(Optional::isPresent)`? If it's absent the caller probably assumed
  // that the values are present. (If we drop it, we should rewrite vacuous filter steps.)
  static final class StreamFlatMapOptional {
    @BeforeTemplate
    Stream before(Stream> stream) {
      return Refaster.anyOf(
          stream.filter(Optional::isPresent).map(Optional::orElseThrow),
          stream.flatMap(Streams::stream));
    }

    @AfterTemplate
    Stream after(Stream> stream) {
      return stream.flatMap(Optional::stream);
    }
  }

  /**
   * Within a stream's map operation unconditional {@link Optional#orElseThrow()} calls can be
   * avoided.
   *
   * 

Warning: this rewrite rule is not completely behavior preserving. The * original code throws an exception if the mapping operation does not produce a value, while the * replacement does not. */ // XXX: An alternative approach is to use `.flatMap(Optional::stream)`. That may be a bit longer, // but yields nicer code. Think about it. abstract static class StreamMapToOptionalGet { @Placeholder abstract Optional toOptionalFunction(@MayOptionallyUse T element); @BeforeTemplate @SuppressWarnings("NullAway") Stream before(Stream stream) { return stream.map(e -> toOptionalFunction(e).orElseThrow()); } @AfterTemplate Stream after(Stream stream) { return stream.flatMap(e -> toOptionalFunction(e).stream()); } } /** Avoid unnecessary nesting of {@link Optional#filter(Predicate)} operations. */ abstract static class FilterOuterOptionalAfterFlatMap { @Placeholder abstract Optional toOptionalFunction(@MayOptionallyUse T element); @BeforeTemplate Optional before(Optional optional, Predicate predicate) { return optional.flatMap(v -> toOptionalFunction(v).filter(predicate)); } @AfterTemplate Optional after(Optional optional, Predicate predicate) { return optional.flatMap(v -> toOptionalFunction(v)).filter(predicate); } } /** Avoid unnecessary nesting of {@link Optional#map(Function)} operations. */ abstract static class MapOuterOptionalAfterFlatMap { @Placeholder abstract Optional toOptionalFunction(@MayOptionallyUse T element); @BeforeTemplate Optional before(Optional optional, Function function) { return optional.flatMap(v -> toOptionalFunction(v).map(function)); } @AfterTemplate Optional after(Optional optional, Function function) { return optional.flatMap(v -> toOptionalFunction(v)).map(function); } } /** Avoid unnecessary nesting of {@link Optional#flatMap(Function)} operations. */ abstract static class FlatMapOuterOptionalAfterFlatMap { @Placeholder abstract Optional toOptionalFunction(@MayOptionallyUse T element); @BeforeTemplate Optional before(Optional optional, Function> function) { return optional.flatMap(v -> toOptionalFunction(v).flatMap(function)); } @AfterTemplate Optional after(Optional optional, Function> function) { return optional.flatMap(v -> toOptionalFunction(v)).flatMap(function); } } /** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */ static final class OptionalOrOtherOptional { @BeforeTemplate @SuppressWarnings("NestedOptionals") Optional before(Optional optional1, Optional optional2) { // XXX: Note that rewriting the first and third variant will change the code's behavior if // `optional2` has side-effects. // XXX: Note that rewriting the first, third and fourth variant will introduce a compilation // error if `optional2` is not effectively final. Review whether a `@Matcher` can be used to // avoid this. return Refaster.anyOf( optional1.map(Optional::of).orElse(optional2), optional1.map(Optional::of).orElseGet(() -> optional2), Stream.of(optional1, optional2).flatMap(Optional::stream).findFirst(), optional1.isPresent() ? optional1 : optional2); } @AfterTemplate Optional after(Optional optional1, Optional optional2) { return optional1.or(() -> optional2); } } /** * Avoid unnecessary operations on an {@link Optional} that ultimately result in that very same * {@link Optional}. */ static final class OptionalIdentity { @BeforeTemplate @SuppressWarnings("NestedOptionals") Optional before(Optional optional, Comparator comparator) { return Refaster.anyOf( optional.or(Refaster.anyOf(() -> Optional.empty(), Optional::empty)), optional .map(Optional::of) .orElseGet(Refaster.anyOf(() -> Optional.empty(), Optional::empty)), optional.stream().findFirst(), optional.stream().findAny(), optional.stream().min(comparator), optional.stream().max(comparator)); } @AfterTemplate @CanIgnoreReturnValue Optional after(Optional optional) { return optional; } } /** * Avoid unnecessary {@link Optional} to {@link Stream} conversion when filtering a value of the * former type. */ static final class OptionalFilter { @BeforeTemplate Optional before(Optional optional, Predicate predicate) { return Refaster.anyOf( optional.stream().filter(predicate).findFirst(), optional.stream().filter(predicate).findAny()); } @AfterTemplate Optional after(Optional optional, Predicate predicate) { return optional.filter(predicate); } } /** * Avoid unnecessary {@link Optional} to {@link Stream} conversion when mapping a value of the * former type. */ // XXX: If `StreamMapFirst` also simplifies `.findAny()` expressions, then this rule can be // dropped in favour of `StreamMapFirst` and `OptionalIdentity`. static final class OptionalMap { @BeforeTemplate Optional before(Optional optional, Function function) { return optional.stream().map(function).findAny(); } @AfterTemplate Optional after(Optional optional, Function function) { return optional.map(function); } } /** Prefer {@link Optional#stream()} over more contrived alternatives. */ static final class OptionalStream { @BeforeTemplate Stream before(Optional optional) { return optional.map(Stream::of).orElseGet(Stream::empty); } @AfterTemplate Stream after(Optional optional) { return optional.stream(); } } // XXX: Add a rule for: // `optional.flatMap(x -> pred(x) ? Optional.empty() : Optional.of(x))` and variants. // (Maybe canonicalize the inner expression. Maybe we rewrite it already.) }