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

tech.picnic.errorprone.refasterrules.StringRules 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 static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.base.Utf8;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;

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

  /** Prefer {@link String#isEmpty()} over alternatives that consult the string's length. */
  // XXX: Now that we build with JDK 15+, this rule can be generalized to cover all `CharSequence`
  // subtypes. This does require a mechanism (perhaps an annotation, or a separate Maven module) to
  // make sure that non-String expressions are rewritten only if client code also targets JDK 15+.
  static final class StringIsEmpty {
    @BeforeTemplate
    boolean before(String str) {
      return Refaster.anyOf(str.length() == 0, str.length() <= 0, str.length() < 1);
    }

    @AfterTemplate
    @AlsoNegation
    boolean after(String str) {
      return str.isEmpty();
    }
  }

  /** Prefer a method reference to {@link String#isEmpty()} over the equivalent lambda function. */
  // XXX: Now that we build with JDK 15+, this rule can be generalized to cover all `CharSequence`
  // subtypes. However, `CharSequence::isEmpty` isn't as nice as `String::isEmpty`, so we might want
  // to introduce a rule that suggests `String::isEmpty` where possible.
  // XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
  // If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.
  static final class StringIsEmptyPredicate {
    @BeforeTemplate
    Predicate before() {
      return s -> s.isEmpty();
    }

    @AfterTemplate
    Predicate after() {
      return String::isEmpty;
    }
  }

  /** Prefer a method reference to {@link String#isEmpty()} over the equivalent lambda function. */
  // XXX: Now that we build with JDK 15+, this rule can be generalized to cover all `CharSequence`
  // subtypes. However, `CharSequence::isEmpty` isn't as nice as `String::isEmpty`, so we might want
  // to introduce a rule that suggests `String::isEmpty` where possible.
  static final class StringIsNotEmptyPredicate {
    @BeforeTemplate
    Predicate before() {
      return s -> !s.isEmpty();
    }

    @AfterTemplate
    @UseImportPolicy(STATIC_IMPORT_ALWAYS)
    Predicate after() {
      return not(String::isEmpty);
    }
  }

  /** Prefer {@link Strings#isNullOrEmpty(String)} over the more verbose alternative. */
  static final class StringIsNullOrEmpty {
    @BeforeTemplate
    boolean before(@Nullable String str) {
      return str == null || str.isEmpty();
    }

    @AfterTemplate
    @AlsoNegation
    boolean after(String str) {
      return Strings.isNullOrEmpty(str);
    }
  }

  /** Don't use the ternary operator to create an optionally-absent string. */
  // XXX: This is a special case of `TernaryOperatorOptionalNegativeFiltering`.
  static final class OptionalNonEmptyString {
    @BeforeTemplate
    Optional before(String str) {
      return Strings.isNullOrEmpty(str)
          ? Optional.empty()
          : Refaster.anyOf(Optional.of(str), Optional.ofNullable(str));
    }

    @AfterTemplate
    Optional after(String str) {
      return Optional.ofNullable(str).filter(not(String::isEmpty));
    }
  }

  static final class FilterEmptyString {
    @BeforeTemplate
    Optional before(Optional optional) {
      return optional.map(Strings::emptyToNull);
    }

    @AfterTemplate
    @UseImportPolicy(STATIC_IMPORT_ALWAYS)
    Optional after(Optional optional) {
      return optional.filter(not(String::isEmpty));
    }
  }

  /** Prefer {@link String#join(CharSequence, Iterable)} and variants over the Guava alternative. */
  // XXX: Joiner.on(char) isn't rewritten. Add separate rule?
  // XXX: Joiner#join(@Nullable Object first, @Nullable Object second, Object... rest) isn't
  // rewritten.
  static final class JoinStrings {
    @BeforeTemplate
    String before(String delimiter, CharSequence[] elements) {
      return Refaster.anyOf(
          Joiner.on(delimiter).join(elements), Arrays.stream(elements).collect(joining(delimiter)));
    }

    @BeforeTemplate
    String before(String delimiter, Iterable elements) {
      return Refaster.anyOf(
          Joiner.on(delimiter).join(elements),
          Streams.stream(elements).collect(joining(delimiter)));
    }

    @BeforeTemplate
    String before(CharSequence delimiter, Collection elements) {
      return elements.stream().collect(joining(delimiter));
    }

    @AfterTemplate
    String after(CharSequence delimiter, Iterable elements) {
      return String.join(delimiter, elements);
    }
  }

  /**
   * Prefer direct invocation of {@link String#valueOf(Object)} over the indirection introduced by
   * {@link Objects#toString(Object)}.
   */
  static final class StringValueOf {
    @BeforeTemplate
    String before(Object object) {
      return Objects.toString(object);
    }

    @AfterTemplate
    String after(Object object) {
      return String.valueOf(object);
    }
  }

  /**
   * Prefer direct invocation of {@link String#String(char[], int, int)} over the indirection
   * introduced by alternatives.
   */
  static final class NewStringFromCharArraySubSequence {
    @BeforeTemplate
    String before(char[] data, int offset, int count) {
      return Refaster.anyOf(
          String.valueOf(data, offset, count), String.copyValueOf(data, offset, count));
    }

    @AfterTemplate
    String after(char[] data, int offset, int count) {
      return new String(data, offset, count);
    }
  }

  /**
   * Prefer direct invocation of {@link String#String(char[])} over the indirection introduced by
   * alternatives.
   */
  static final class NewStringFromCharArray {
    @BeforeTemplate
    String before(char[] data) {
      return Refaster.anyOf(String.valueOf(data), new String(data, 0, data.length));
    }

    @AfterTemplate
    String after(char[] data) {
      return new String(data);
    }
  }

  /**
   * Prefer direct delegation to {@link String#valueOf(Object)} over the indirection introduced by
   * {@link Objects#toString(Object)}.
   */
  // XXX: This rule is analogous to `StringValueOf` above. Arguably this is its generalization.
  // If/when Refaster is extended to understand this, delete the rule above.
  static final class StringValueOfMethodReference {
    @BeforeTemplate
    Function before() {
      return Objects::toString;
    }

    @AfterTemplate
    Function after() {
      return String::valueOf;
    }
  }

  /** Don't unnecessarily use the two-argument {@link String#substring(int, int)}. */
  static final class SubstringRemainder {
    @BeforeTemplate
    String before(String str, int index) {
      return str.substring(index, str.length());
    }

    @AfterTemplate
    String after(String str, int index) {
      return str.substring(index);
    }
  }

  /** Prefer {@link Utf8#encodedLength(CharSequence)} over less efficient alternatives. */
  static final class Utf8EncodedLength {
    @BeforeTemplate
    int before(String str) {
      return str.getBytes(UTF_8).length;
    }

    @AfterTemplate
    int after(String str) {
      return Utf8.encodedLength(str);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy