Compare commits

..

2 Commits

Author SHA1 Message Date
Stephan Schroevers
fd9c248777 WIP 2: Other spec ideation 2024-08-23 22:43:27 +02:00
Stephan Schroevers
9954663bd5 WIP: Doodles 2024-08-23 22:43:27 +02:00
30 changed files with 522 additions and 592 deletions

View File

@@ -46,7 +46,7 @@ jobs:
with:
java-version: ${{ matrix.jdk }}
java-distribution: ${{ matrix.distribution }}
maven-version: 3.9.9
maven-version: 3.9.8
- name: Display build environment details
run: mvn --version
- name: Build project against vanilla Error Prone, compile Javadoc

View File

@@ -38,7 +38,7 @@ jobs:
with:
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.9
maven-version: 3.9.8
- name: Initialize CodeQL
uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
with:

View File

@@ -27,7 +27,7 @@ jobs:
checkout-fetch-depth: 2
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.9
maven-version: 3.9.8
- name: Run Pitest
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
# analyzes lines changed in the associated pull request, as GitHub

View File

@@ -35,7 +35,7 @@ jobs:
with:
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.9
maven-version: 3.9.8
- name: Download Pitest analysis artifact
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
with:

View File

@@ -38,7 +38,7 @@ jobs:
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.9
maven-version: 3.9.8
- name: Install project to local Maven repository
run: mvn -T1C install -DskipTests -Dverification.skip
- name: Run integration test

View File

@@ -40,7 +40,7 @@ jobs:
checkout-fetch-depth: 0
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.9
maven-version: 3.9.8
- name: Create missing `test` directory
# XXX: Drop this step in favour of actually having a test.
run: mkdir refaster-compiler/src/test

View File

@@ -67,11 +67,6 @@
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>

View File

@@ -18,12 +18,14 @@ import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.Optional;
import java.util.function.Supplier;
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
import tech.picnic.errorprone.utils.SourceCode;
/**
@@ -34,12 +36,12 @@ import tech.picnic.errorprone.utils.SourceCode;
* it does, the suggested fix changes the program's semantics. Such fragile code must instead be
* refactored such that the side-effectful code does not appear accidental.
*/
// 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 the `MethodReferenceUsageCheck` bug checker becomes generally usable, consider leaving
// the method reference cleanup to that check, and express the remainder of the logic in this class
// using a Refaster template, i.c.w. a `@NotMatches(RequiresComputation.class)` constraint.
// XXX: Consider also implementing the inverse, in which `.orElseGet(() -> someConstant)` is
// flagged.
// XXX: Once the `MethodReferenceUsageCheck` becomes generally usable, consider leaving the method
// reference cleanup to that check, and express the remainder of the logic in this class using a
// Refaster template, i.c.w. a `@Matches` constraint that implements the `requiresComputation`
// logic.
@AutoService(BugChecker.class)
@BugPattern(
summary =
@@ -49,17 +51,16 @@ import tech.picnic.errorprone.utils.SourceCode;
linkType = NONE,
severity = WARNING,
tags = PERFORMANCE)
public final class OptionalOrElseGet extends BugChecker implements MethodInvocationTreeMatcher {
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> REQUIRES_COMPUTATION = new RequiresComputation();
private static final Matcher<ExpressionTree> OPTIONAL_OR_ELSE_METHOD =
instanceMethod().onExactClass(Optional.class.getCanonicalName()).namedAnyOf("orElse");
// XXX: Also exclude invocations of `@Placeholder`-annotated methods.
private static final Matcher<ExpressionTree> REFASTER_METHOD =
staticMethod().onClass(Refaster.class.getCanonicalName());
/** Instantiates a new {@link OptionalOrElseGet} instance. */
public OptionalOrElseGet() {}
/** Instantiates a new {@link OptionalOrElse} instance. */
public OptionalOrElse() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
@@ -68,8 +69,7 @@ public final class OptionalOrElseGet extends BugChecker implements MethodInvocat
}
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
if (!REQUIRES_COMPUTATION.matches(argument, state)
|| REFASTER_METHOD.matches(argument, state)) {
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
return Description.NO_MATCH;
}
@@ -91,6 +91,18 @@ public final class OptionalOrElseGet extends BugChecker implements MethodInvocat
return describeMatch(tree, fix);
}
/**
* Tells whether the given expression contains anything other than a literal or a (possibly
* dereferenced) variable or constant.
*/
private static boolean requiresComputation(ExpressionTree tree) {
return !(tree instanceof IdentifierTree
|| tree instanceof LiteralTree
|| (tree instanceof MemberSelectTree memberSelect
&& !requiresComputation(memberSelect.getExpression()))
|| ASTHelpers.constValue(tree) != null);
}
/** Returns the nullary method reference matching the given expression, if any. */
private static Optional<String> tryMethodReferenceConversion(
ExpressionTree tree, VisitorState state) {
@@ -106,7 +118,7 @@ public final class OptionalOrElseGet extends BugChecker implements MethodInvocat
return Optional.empty();
}
if (REQUIRES_COMPUTATION.matches(memberSelect.getExpression(), state)) {
if (requiresComputation(memberSelect.getExpression())) {
return Optional.empty();
}

View File

@@ -27,7 +27,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
@@ -245,33 +244,6 @@ final class ComparatorRules {
}
}
/** Prefer {@link Collections#sort(List)} over more verbose alternatives. */
static final class CollectionsSort<T extends Comparable<? super T>> {
@BeforeTemplate
void before(List<T> collection) {
Collections.sort(collection, naturalOrder());
}
@AfterTemplate
void after(List<T> collection) {
Collections.sort(collection);
}
}
/** Prefer {@link Collections#min(Collection)} over more verbose alternatives. */
static final class CollectionsMin<T extends Comparable<? super T>> {
@BeforeTemplate
T before(Collection<T> collection) {
return Refaster.anyOf(
Collections.min(collection, naturalOrder()), Collections.max(collection, reverseOrder()));
}
@AfterTemplate
T after(Collection<T> collection) {
return Collections.min(collection);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
@@ -292,7 +264,7 @@ final class ComparatorRules {
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class CollectionsMinWithComparator<S, T extends S> {
static final class MinOfCollection<S, T extends S> {
@BeforeTemplate
T before(Collection<T> collection, Comparator<S> cmp) {
return collection.stream().min(cmp).orElseThrow();
@@ -371,20 +343,6 @@ final class ComparatorRules {
}
}
/** Prefer {@link Collections#max(Collection)} over more verbose alternatives. */
static final class CollectionsMax<T extends Comparable<? super T>> {
@BeforeTemplate
T before(Collection<T> collection) {
return Refaster.anyOf(
Collections.max(collection, naturalOrder()), Collections.min(collection, reverseOrder()));
}
@AfterTemplate
T after(Collection<T> collection) {
return Collections.max(collection);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
@@ -405,7 +363,7 @@ final class ComparatorRules {
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class CollectionsMaxWithComparator<S, T extends S> {
static final class MaxOfCollection<S, T extends S> {
@BeforeTemplate
T before(Collection<T> collection, Comparator<S> cmp) {
return collection.stream().max(cmp).orElseThrow();

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.refaster.Refaster;
@@ -11,6 +12,10 @@ 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Optional;
@@ -20,13 +25,30 @@ 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.RequiresComputation;
import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;
/** Refaster rules related to expressions dealing with {@link Optional}s. */
@OnlineDocumentation
final class OptionalRules {
private OptionalRules() {}
// XXX: Move or drop.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface BeforeExample {}
// XXX: Move or drop.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@interface AfterExample {}
// XXX: Move or drop.
interface RefasterExpressionExamples<T> {
ImmutableList<T> before();
ImmutableList<T> after();
}
/** Prefer {@link Optional#empty()} over the more contrived alternative. */
static final class OptionalEmpty<T> {
@BeforeTemplate
@@ -53,6 +75,40 @@ final class OptionalRules {
Optional<T> after(T object) {
return Optional.ofNullable(object);
}
@BeforeExample
@SuppressWarnings({
"TernaryOperatorOptionalNegativeFiltering",
"TernaryOperatorOptionalPositiveFiltering"
} /* Special cases. */)
ImmutableList<Optional<String>> input() {
return ImmutableList.of(
toString() == null ? Optional.empty() : Optional.of(toString()),
toString() != null ? Optional.of(toString()) : Optional.empty());
}
@AfterExample
ImmutableList<Optional<String>> output() {
return ImmutableList.of(Optional.ofNullable(toString()), Optional.ofNullable(toString()));
}
static final class Examples implements RefasterExpressionExamples<Optional<String>> {
@Override
@SuppressWarnings({
"TernaryOperatorOptionalNegativeFiltering",
"TernaryOperatorOptionalPositiveFiltering"
} /* Special cases. */)
public ImmutableList<Optional<String>> before() {
return ImmutableList.of(
toString() == null ? Optional.empty() : Optional.of(toString()),
toString() != null ? Optional.of(toString()) : Optional.empty());
}
@Override
public ImmutableList<Optional<String>> after() {
return ImmutableList.of(Optional.ofNullable(toString()), Optional.ofNullable(toString()));
}
}
}
/** Prefer {@link Optional#isEmpty()} over the more verbose alternative. */
@@ -66,6 +122,16 @@ final class OptionalRules {
boolean after(Optional<T> optional) {
return optional.isEmpty();
}
@BeforeExample
ImmutableList<Boolean> input() {
return ImmutableList.of(!Optional.empty().isPresent(), !Optional.of("foo").isPresent());
}
@AfterExample
ImmutableList<Boolean> output() {
return ImmutableList.of(Optional.empty().isEmpty(), Optional.of("foo").isEmpty());
}
}
/** Prefer {@link Optional#isPresent()} over the inverted alternative. */
@@ -79,6 +145,16 @@ final class OptionalRules {
boolean after(Optional<T> optional) {
return optional.isPresent();
}
@BeforeExample
ImmutableList<Boolean> input() {
return ImmutableList.of(!Optional.empty().isEmpty(), !Optional.of("foo").isEmpty());
}
@AfterExample
ImmutableList<Boolean> output() {
return ImmutableList.of(Optional.empty().isPresent(), Optional.of("foo").isPresent());
}
}
/** Prefer {@link Optional#orElseThrow()} over the less explicit {@link Optional#get()}. */
@@ -255,21 +331,24 @@ final class OptionalRules {
}
/**
* Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the
* fallback value does not require non-trivial computation.
* 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 is the counterpart to the `OptionalOrElseGet` bug checker. Once the
// `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be
// replaced with a Refaster rule.
static final class OptionalOrElse<T> {
// 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<T> {
@BeforeTemplate
T before(Optional<T> optional, @NotMatches(RequiresComputation.class) T value) {
return optional.orElseGet(() -> value);
T before(Optional<T> optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
return optional.orElse(value);
}
@AfterTemplate
T after(Optional<T> optional, T value) {
return optional.orElse(value);
return optional.orElseGet(() -> value);
}
}
@@ -370,12 +449,7 @@ final class OptionalRules {
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
static final class OptionalOrOtherOptional<T> {
@BeforeTemplate
@SuppressWarnings({
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
"NestedOptionals" /* This violation will be rewritten. */,
"OptionalOrElse" /* Parameters represent expressions that may require computation. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
@SuppressWarnings("NestedOptionals")
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
// XXX: Note that rewriting the first and third variant will change the code's behavior if
// `optional2` has side-effects.

View File

@@ -8,8 +8,6 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
@@ -78,8 +76,6 @@ final class PrimitiveRules {
}
/** Prefer {@link Math#toIntExact(long)} over the Guava alternative. */
// XXX: This rule changes the exception possibly thrown from `IllegalArgumentException` to
// `ArithmeticException`.
static final class LongToIntExact {
@BeforeTemplate
int before(long l) {
@@ -196,8 +192,72 @@ final class PrimitiveRules {
}
}
/** Prefer {@link Boolean#compare(boolean, boolean)} over the Guava alternative. */
static final class BooleanCompare {
@BeforeTemplate
int before(boolean a, boolean b) {
return Booleans.compare(a, b);
}
@AfterTemplate
int after(boolean a, boolean b) {
return Boolean.compare(a, b);
}
}
/** Prefer {@link Character#compare(char, char)} over the Guava alternative. */
static final class CharacterCompare {
@BeforeTemplate
int before(char a, char b) {
return Chars.compare(a, b);
}
@AfterTemplate
int after(char a, char b) {
return Character.compare(a, b);
}
}
/** Prefer {@link Short#compare(short, short)} over the Guava alternative. */
static final class ShortCompare {
@BeforeTemplate
int before(short a, short b) {
return Shorts.compare(a, b);
}
@AfterTemplate
int after(short a, short b) {
return Short.compare(a, b);
}
}
/** Prefer {@link Integer#compare(int, int)} over the Guava alternative. */
static final class IntegerCompare {
@BeforeTemplate
int before(int a, int b) {
return Ints.compare(a, b);
}
@AfterTemplate
int after(int a, int b) {
return Integer.compare(a, b);
}
}
/** Prefer {@link Long#compare(long, long)} over the Guava alternative. */
static final class LongCompare {
@BeforeTemplate
int before(long a, long b) {
return Longs.compare(a, b);
}
@AfterTemplate
int after(long a, long b) {
return Long.compare(a, b);
}
}
/** Prefer {@link Float#compare(float, float)} over the Guava alternative. */
// XXX: Drop this rule once https://github.com/google/guava/pull/7371 is released.
static final class FloatCompare {
@BeforeTemplate
int before(float a, float b) {
@@ -211,7 +271,6 @@ final class PrimitiveRules {
}
/** Prefer {@link Double#compare(double, double)} over the Guava alternative. */
// XXX: Drop this rule once https://github.com/google/guava/pull/7371 is released.
static final class DoubleCompare {
@BeforeTemplate
int before(double a, double b) {
@@ -383,205 +442,4 @@ final class PrimitiveRules {
return Long.signum(l) == -1;
}
}
/** Prefer JDK's {@link Integer#compareUnsigned(int, int)} over third-party alternatives. */
static final class IntegerCompareUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.compare(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.compareUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#compareUnsigned(long, long)} over third-party alternatives. */
static final class LongCompareUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.compare(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.compareUnsigned(x, y);
}
}
/** Prefer JDK's {@link Integer#divideUnsigned(int, int)} over third-party alternatives. */
static final class IntegerDivideUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.divide(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.divideUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#divideUnsigned(long, long)} over third-party alternatives. */
static final class LongDivideUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.divide(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.divideUnsigned(x, y);
}
}
/** Prefer JDK's {@link Integer#remainderUnsigned(int, int)} over third-party alternatives. */
static final class IntegerRemainderUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.remainder(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.remainderUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#remainderUnsigned(long, long)} over third-party alternatives. */
static final class LongRemainderUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.remainder(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.remainderUnsigned(x, y);
}
}
/**
* Prefer JDK's {@link Integer#parseUnsignedInt(String)} over third-party or more verbose
* alternatives.
*/
static final class IntegerParseUnsignedInt {
@BeforeTemplate
int before(String string) {
return Refaster.anyOf(
UnsignedInts.parseUnsignedInt(string), Integer.parseUnsignedInt(string, 10));
}
@AfterTemplate
int after(String string) {
return Integer.parseUnsignedInt(string);
}
}
/**
* Prefer JDK's {@link Long#parseUnsignedLong(String)} over third-party or more verbose
* alternatives.
*/
static final class LongParseUnsignedLong {
@BeforeTemplate
long before(String string) {
return Refaster.anyOf(
UnsignedLongs.parseUnsignedLong(string), Long.parseUnsignedLong(string, 10));
}
@AfterTemplate
long after(String string) {
return Long.parseUnsignedLong(string);
}
}
/** Prefer JDK's {@link Integer#parseUnsignedInt(String, int)} over third-party alternatives. */
static final class IntegerParseUnsignedIntWithRadix {
@BeforeTemplate
int before(String string, int radix) {
return UnsignedInts.parseUnsignedInt(string, radix);
}
@AfterTemplate
int after(String string, int radix) {
return Integer.parseUnsignedInt(string, radix);
}
}
/** Prefer JDK's {@link Long#parseUnsignedLong(String, int)} over third-party alternatives. */
static final class LongParseUnsignedLongWithRadix {
@BeforeTemplate
long before(String string, int radix) {
return UnsignedLongs.parseUnsignedLong(string, radix);
}
@AfterTemplate
long after(String string, int radix) {
return Long.parseUnsignedLong(string, radix);
}
}
/**
* Prefer JDK's {@link Integer#toUnsignedString(int)} over third-party or more verbose
* alternatives.
*/
static final class IntegerToUnsignedString {
@BeforeTemplate
String before(int i) {
return Refaster.anyOf(UnsignedInts.toString(i), Integer.toUnsignedString(i, 10));
}
@AfterTemplate
String after(int i) {
return Integer.toUnsignedString(i);
}
}
/**
* Prefer JDK's {@link Long#toUnsignedString(long)} over third-party or more verbose alternatives.
*/
static final class LongToUnsignedString {
@BeforeTemplate
String before(long i) {
return Refaster.anyOf(UnsignedLongs.toString(i), Long.toUnsignedString(i, 10));
}
@AfterTemplate
String after(long i) {
return Long.toUnsignedString(i);
}
}
/**
* Prefer JDK's {@link Integer#toUnsignedString(int,int)} over third-party or more verbose
* alternatives.
*/
static final class IntegerToUnsignedStringWithRadix {
@BeforeTemplate
String before(int i, int radix) {
return UnsignedInts.toString(i, radix);
}
@AfterTemplate
String after(int i, int radix) {
return Integer.toUnsignedString(i, radix);
}
}
/**
* Prefer JDK's {@link Long#toUnsignedString(long,int)} over third-party or more verbose
* alternatives.
*/
static final class LongToUnsignedStringWithRadix {
@BeforeTemplate
String before(long i, int radix) {
return UnsignedLongs.toString(i, radix);
}
@AfterTemplate
String after(long i, int radix) {
return Long.toUnsignedString(i, radix);
}
}
}

View File

@@ -13,7 +13,6 @@ import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -1957,22 +1956,6 @@ final class ReactorRules {
}
}
/**
* Don't propagate {@link Mono} cancellations to an upstream cache value computation, as
* completion of such computations may benefit concurrent or subsequent cache usages.
*/
static final class MonoFromFutureAsyncLoadingCacheGet<K, V> {
@BeforeTemplate
Mono<V> before(AsyncLoadingCache<K, V> cache, K key) {
return Mono.fromFuture(() -> cache.get(key));
}
@AfterTemplate
Mono<V> after(AsyncLoadingCache<K, V> cache, K key) {
return Mono.fromFuture(() -> cache.get(key), /* suppressCancel= */ true);
}
}
/**
* Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former
* yields a {@link Flux} that is more likely to behave as expected when subscribed to more than

View File

@@ -244,11 +244,4 @@ final class StringRules {
return Utf8.encodedLength(str);
}
}
// Rewrite `String#copyValueOf(char[])` to `new String(char[])`.
// Example:
// String.copyValueOf(new char[]);
// -->
// new String(new char[]);
}

View File

@@ -5,15 +5,14 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class OptionalOrElseGetTest {
final class OptionalOrElseTest {
@Test
void identification() {
CompilationTestHelper.newInstance(OptionalOrElseGet.class, getClass())
CompilationTestHelper.newInstance(OptionalOrElse.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",
"import java.util.Optional;",
"import java.util.function.Supplier;",
"",
"class A {",
" private final Optional<Object> optional = Optional.empty();",
@@ -28,7 +27,6 @@ final class OptionalOrElseGetTest {
" optional.orElse(string);",
" optional.orElse(this.string);",
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
"",
" // BUG: Diagnostic contains:",
" Optional.empty().orElse(string + \"constant\");",
@@ -69,7 +67,7 @@ final class OptionalOrElseGetTest {
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElseGet.class, getClass())
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElse.class, getClass())
.addInputLines(
"A.java",
"import java.util.Optional;",

View File

@@ -107,21 +107,11 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>reverseOrder().compare("baz", "qux"));
}
void testCollectionsSort() {
Collections.sort(ImmutableList.of("foo", "bar"), naturalOrder());
}
ImmutableSet<String> testCollectionsMin() {
return ImmutableSet.of(
Collections.min(ImmutableList.of("foo"), naturalOrder()),
Collections.max(ImmutableList.of("bar"), reverseOrder()));
}
String testMinOfArray() {
return Arrays.stream(new String[0]).min(naturalOrder()).orElseThrow();
}
String testCollectionsMinWithComparator() {
String testMinOfCollection() {
return ImmutableSet.of("foo", "bar").stream().min(naturalOrder()).orElseThrow();
}
@@ -153,17 +143,11 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
ImmutableSet<String> testCollectionsMax() {
return ImmutableSet.of(
Collections.max(ImmutableList.of("foo"), naturalOrder()),
Collections.min(ImmutableList.of("bar"), reverseOrder()));
}
String testMaxOfArray() {
return Arrays.stream(new String[0]).max(naturalOrder()).orElseThrow();
}
String testCollectionsMaxWithComparator() {
String testMaxOfCollection() {
return ImmutableSet.of("foo", "bar").stream().max(naturalOrder()).orElseThrow();
}

View File

@@ -98,20 +98,11 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
}
void testCollectionsSort() {
Collections.sort(ImmutableList.of("foo", "bar"));
}
ImmutableSet<String> testCollectionsMin() {
return ImmutableSet.of(
Collections.min(ImmutableList.of("foo")), Collections.min(ImmutableList.of("bar")));
}
String testMinOfArray() {
return Collections.min(Arrays.asList(new String[0]), naturalOrder());
}
String testCollectionsMinWithComparator() {
String testMinOfCollection() {
return Collections.min(ImmutableSet.of("foo", "bar"), naturalOrder());
}
@@ -143,16 +134,11 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparators.min("a", "b", (a, b) -> 1));
}
ImmutableSet<String> testCollectionsMax() {
return ImmutableSet.of(
Collections.max(ImmutableList.of("foo")), Collections.max(ImmutableList.of("bar")));
}
String testMaxOfArray() {
return Collections.max(Arrays.asList(new String[0]), naturalOrder());
}
String testCollectionsMaxWithComparator() {
String testMaxOfCollection() {
return Collections.max(ImmutableSet.of("foo", "bar"), naturalOrder());
}

View File

@@ -83,9 +83,11 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of("foo").orElseGet(() -> Optional.of("bar").orElseThrow());
}
ImmutableSet<String> testOptionalOrElse() {
ImmutableSet<String> testOptionalOrElseGet() {
return ImmutableSet.of(
Optional.of("foo").orElseGet(() -> "bar"), Optional.of("baz").orElseGet(() -> toString()));
Optional.of("foo").orElse("bar"),
Optional.of("baz").orElse(toString()),
Optional.of("qux").orElse(String.valueOf(true)));
}
ImmutableSet<Object> testStreamFlatMapOptional() {

View File

@@ -80,9 +80,11 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of("foo").or(() -> Optional.of("bar")).orElseThrow();
}
ImmutableSet<String> testOptionalOrElse() {
ImmutableSet<String> testOptionalOrElseGet() {
return ImmutableSet.of(
Optional.of("foo").orElse("bar"), Optional.of("baz").orElseGet(() -> toString()));
Optional.of("foo").orElse("bar"),
Optional.of("baz").orElse(toString()),
Optional.of("qux").orElseGet(() -> String.valueOf(true)));
}
ImmutableSet<Object> testStreamFlatMapOptional() {

View File

@@ -9,8 +9,6 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
@@ -24,9 +22,7 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
Floats.class,
Ints.class,
Longs.class,
Shorts.class,
UnsignedInts.class,
UnsignedLongs.class);
Shorts.class);
}
ImmutableSet<Boolean> testLessThan() {
@@ -109,6 +105,26 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return Doubles.hashCode(1);
}
int testBooleanCompare() {
return Booleans.compare(false, true);
}
int testCharacterCompare() {
return Chars.compare('a', 'b');
}
int testShortCompare() {
return Shorts.compare((short) 1, (short) 2);
}
int testIntegerCompare() {
return Ints.compare(1, 2);
}
int testLongCompare() {
return Longs.compare(1, 2);
}
int testFloatCompare() {
return Floats.compare(1, 2);
}
@@ -174,60 +190,4 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Long.signum(1L) < 0, Long.signum(2L) <= -1, Long.signum(3L) >= 0, Long.signum(4L) > -1);
}
int testIntegerCompareUnsigned() {
return UnsignedInts.compare(1, 2);
}
long testLongCompareUnsigned() {
return UnsignedLongs.compare(1, 2);
}
int testIntegerDivideUnsigned() {
return UnsignedInts.divide(1, 2);
}
long testLongDivideUnsigned() {
return UnsignedLongs.divide(1, 2);
}
int testIntegerRemainderUnsigned() {
return UnsignedInts.remainder(1, 2);
}
long testLongRemainderUnsigned() {
return UnsignedLongs.remainder(1, 2);
}
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
return ImmutableSet.of(UnsignedInts.parseUnsignedInt("1"), Integer.parseUnsignedInt("2", 10));
}
ImmutableSet<Long> testLongParseUnsignedLong() {
return ImmutableSet.of(UnsignedLongs.parseUnsignedLong("1"), Long.parseUnsignedLong("2", 10));
}
int testIntegerParseUnsignedIntWithRadix() {
return UnsignedInts.parseUnsignedInt("1", 2);
}
long testLongParseUnsignedLongWithRadix() {
return UnsignedLongs.parseUnsignedLong("1", 2);
}
ImmutableSet<String> testIntegerToUnsignedString() {
return ImmutableSet.of(UnsignedInts.toString(1), Integer.toUnsignedString(2, 10));
}
ImmutableSet<String> testLongToUnsignedString() {
return ImmutableSet.of(UnsignedLongs.toString(1), Long.toUnsignedString(2, 10));
}
String testIntegerToUnsignedStringWithRadix() {
return UnsignedInts.toString(1, 2);
}
String testLongToUnsignedStringWithRadix() {
return UnsignedLongs.toString(1, 2);
}
}

View File

@@ -9,8 +9,6 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
@@ -24,9 +22,7 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
Floats.class,
Ints.class,
Longs.class,
Shorts.class,
UnsignedInts.class,
UnsignedLongs.class);
Shorts.class);
}
ImmutableSet<Boolean> testLessThan() {
@@ -109,6 +105,26 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return Double.hashCode(1);
}
int testBooleanCompare() {
return Boolean.compare(false, true);
}
int testCharacterCompare() {
return Character.compare('a', 'b');
}
int testShortCompare() {
return Short.compare((short) 1, (short) 2);
}
int testIntegerCompare() {
return Integer.compare(1, 2);
}
int testLongCompare() {
return Long.compare(1, 2);
}
int testFloatCompare() {
return Float.compare(1, 2);
}
@@ -174,60 +190,4 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Long.signum(1L) == -1, Long.signum(2L) == -1, Long.signum(3L) != -1, Long.signum(4L) != -1);
}
int testIntegerCompareUnsigned() {
return Integer.compareUnsigned(1, 2);
}
long testLongCompareUnsigned() {
return Long.compareUnsigned(1, 2);
}
int testIntegerDivideUnsigned() {
return Integer.divideUnsigned(1, 2);
}
long testLongDivideUnsigned() {
return Long.divideUnsigned(1, 2);
}
int testIntegerRemainderUnsigned() {
return Integer.remainderUnsigned(1, 2);
}
long testLongRemainderUnsigned() {
return Long.remainderUnsigned(1, 2);
}
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
return ImmutableSet.of(Integer.parseUnsignedInt("1"), Integer.parseUnsignedInt("2"));
}
ImmutableSet<Long> testLongParseUnsignedLong() {
return ImmutableSet.of(Long.parseUnsignedLong("1"), Long.parseUnsignedLong("2"));
}
int testIntegerParseUnsignedIntWithRadix() {
return Integer.parseUnsignedInt("1", 2);
}
long testLongParseUnsignedLongWithRadix() {
return Long.parseUnsignedLong("1", 2);
}
ImmutableSet<String> testIntegerToUnsignedString() {
return ImmutableSet.of(Integer.toUnsignedString(1), Integer.toUnsignedString(2));
}
ImmutableSet<String> testLongToUnsignedString() {
return ImmutableSet.of(Long.toUnsignedString(1), Long.toUnsignedString(2));
}
String testIntegerToUnsignedStringWithRadix() {
return Integer.toUnsignedString(1, 2);
}
String testLongToUnsignedStringWithRadix() {
return Long.toUnsignedString(1, 2);
}
}

View File

@@ -10,7 +10,6 @@ import static java.util.stream.Collectors.minBy;
import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -665,10 +664,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.fromFuture(CompletableFuture.completedFuture(null), true);
}
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0));
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(Stream.of(1));
}

View File

@@ -12,7 +12,6 @@ import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -645,10 +644,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true);
}
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0), true);
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(() -> Stream.of(1));
}

View File

@@ -96,8 +96,4 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
int testUtf8EncodedLength() {
return "foo".getBytes(UTF_8).length;
}
String testNewStringChar() {
return String.copyValueOf(new char[] {'f', 'o', 'o'});
}
}

View File

@@ -96,8 +96,4 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
int testUtf8EncodedLength() {
return Utf8.encodedLength("foo");
}
String testNewStringChar() {
return new String(new char[] {'f', 'o', 'o'});
}
}

33
pom.xml
View File

@@ -208,16 +208,16 @@
<version.auto-value>1.11.0</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.31.0</version.error-prone-orig>
<version.error-prone-orig>2.30.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.25</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>17</version.jdk>
<version.maven>3.9.9</version.maven>
<version.mockito>5.13.0</version.mockito>
<version.maven>3.9.8</version.maven>
<version.mockito>5.12.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.11.2</version.nullaway>
<version.pitest-git>1.1.4</version.pitest-git>
<version.rewrite-templating>1.14.0</version.rewrite-templating>
<version.rewrite-templating>1.13.0</version.rewrite-templating>
<version.surefire>3.2.3</version.surefire>
</properties>
@@ -300,11 +300,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
@@ -343,7 +338,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>33.3.0-jre</version>
<version>33.2.1-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -382,7 +377,7 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.23</version>
<version>2.2.22</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -417,7 +412,7 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.15.1</version>
<version>1.14.19</version>
</dependency>
<!-- Specified so that Renovate will file Maven upgrade PRs, which
subsequently will cause `maven-enforcer-plugin` to require that
@@ -486,7 +481,7 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.17.0</version>
<version>2.16.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -500,19 +495,19 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.1.12</version>
<version>6.1.11</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>3.3.3</version>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.3.3</version>
<version>6.3.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -567,7 +562,7 @@
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<version>2.24</version>
<version>2.22.1</version>
<configuration>
<additionalSourceDirectories>
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
@@ -653,7 +648,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.5.0</version>
<version>3.4.0</version>
<configuration>
<checkstyleRules>
<!-- We only enable rules that are not enforced by
@@ -902,7 +897,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.18.0</version>
<version>10.17.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>

View File

@@ -2,7 +2,6 @@ package tech.picnic.errorprone.refaster.matchers;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
@@ -10,19 +9,34 @@ import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
/** A matcher of expressions that may a non-trivial amount of computation. */
public final class RequiresComputation implements Matcher<ExpressionTree> {
/** A matcher of expressions that likely require little to no computation. */
public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link RequiresComputation} instance. */
public RequiresComputation() {}
/** Instantiates a new {@link IsLikelyTrivialComputation} instance. */
public IsLikelyTrivialComputation() {}
@Override
public boolean matches(ExpressionTree expressionTree, VisitorState state) {
if (expressionTree instanceof MethodInvocationTree methodInvocation) {
// XXX: Method invocations are generally *not* trivial computations, but we make an exception
// for nullary method invocations on the result of a trivial computation. This exception
// allows this `Matcher` to by the `OptionalOrElseGet` Refaster rule, such that it does not
// suggest the introduction of lambda expressions that are better expressed as method
// references. Once the `MethodReferenceUsage` bug checker is production-ready, this exception
// should be removed. (But at that point, instead defining a `RequiresComputation` matcher may
// be more appropriate.)
if (methodInvocation.getArguments().isEmpty()
&& matches(methodInvocation.getMethodSelect())) {
return true;
}
}
return matches(expressionTree);
}
@@ -30,11 +44,11 @@ public final class RequiresComputation implements Matcher<ExpressionTree> {
// Depending on feedback such trees may be matched in the future.
private static boolean matches(ExpressionTree expressionTree) {
if (expressionTree instanceof ArrayAccessTree arrayAccess) {
return matches(arrayAccess.getExpression()) || matches(arrayAccess.getIndex());
return matches(arrayAccess.getExpression()) && matches(arrayAccess.getIndex());
}
if (expressionTree instanceof LiteralTree) {
return false;
return true;
}
if (expressionTree instanceof LambdaExpressionTree) {
@@ -42,14 +56,11 @@ public final class RequiresComputation implements Matcher<ExpressionTree> {
* Lambda expressions encapsulate computations, but their definition does not involve
* significant computation.
*/
return false;
return true;
}
if (expressionTree instanceof IdentifierTree) {
// XXX: Generally identifiers don't by themselves represent a computation, though they may be
// a stand-in for one if they are a Refaster template method argument. Can we identify such
// cases, also when the `Matcher` is invoked by Refaster?
return false;
return true;
}
if (expressionTree instanceof MemberReferenceTree memberReference) {
@@ -69,11 +80,11 @@ public final class RequiresComputation implements Matcher<ExpressionTree> {
}
if (expressionTree instanceof UnaryTree unary) {
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement
// represent non-trivial computations.
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement are not
// trivial.
return matches(unary.getExpression());
}
return ASTHelpers.constValue(expressionTree) == null;
return false;
}
}

View File

@@ -8,69 +8,54 @@ import com.google.errorprone.bugpatterns.BugChecker;
import com.sun.source.tree.ReturnTree;
import org.junit.jupiter.api.Test;
final class RequiresComputationTest {
final class IsLikelyTrivialComputationTest {
@Test
void matches() {
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import java.io.OutputStream;",
"import java.util.Comparator;",
"import java.util.function.Predicate;",
"",
"class A {",
" int negative1() {",
" int[] arr = new int[0];",
" return arr[0];",
" String negative1() {",
" return String.valueOf(1);",
" }",
"",
" String negative2() {",
" return null;",
" return toString().toString();",
" }",
"",
" boolean negative3() {",
" return false;",
" String negative3() {",
" return \"foo\" + toString();",
" }",
"",
" int negative4() {",
" return 0;",
" byte negative4() {",
" return \"foo\".getBytes()[0];",
" }",
"",
" String negative5() {",
" return \"foo\" + \"bar\";",
" int negative5() {",
" int[] arr = new int[0];",
" return arr[hashCode()];",
" }",
"",
" Predicate<String> negative6() {",
" return v -> \"foo\".equals(v);",
" int negative6() {",
" return 1 * 2;",
" }",
"",
" A negative7() {",
" return this;",
" Predicate<String> negative7() {",
" return toString()::equals;",
" }",
"",
" Predicate<String> negative8() {",
" return \"foo\"::equals;",
" String negative8() {",
" return (toString());",
" }",
"",
" OutputStream negative9() {",
" return System.out;",
" Object negative9() {",
" return (Object) toString();",
" }",
"",
" A negative10() {",
" return (this);",
" }",
"",
" Object negative11() {",
" return (Object) this;",
" }",
"",
" boolean negative12() {",
" boolean[] arr = new boolean[0];",
" return !arr[0];",
" }",
"",
" String negative13() {",
" return \"foo\" + 0;",
" int negative10() {",
" return -hashCode();",
" }",
"",
" String positive1() {",
@@ -83,63 +68,68 @@ final class RequiresComputationTest {
" return this.toString();",
" }",
"",
" String positive3() {",
" int positive3() {",
" int[] arr = new int[0];",
" // BUG: Diagnostic contains:",
" return String.valueOf(1);",
" return arr[0];",
" }",
"",
" String positive4() {",
" // BUG: Diagnostic contains:",
" return toString().toString();",
" return null;",
" }",
"",
" String positive5() {",
" boolean positive5() {",
" // BUG: Diagnostic contains:",
" return \"foo\" + toString();",
" return false;",
" }",
"",
" byte positive6() {",
" int positive6() {",
" // BUG: Diagnostic contains:",
" return \"foo\".getBytes()[0];",
" return 0;",
" }",
"",
" int positive7() {",
" int[] arr = new int[0];",
" String positive7() {",
" // BUG: Diagnostic contains:",
" return arr[hashCode()];",
" return \"foo\" + \"bar\";",
" }",
"",
" Predicate<String> positive8() {",
" // BUG: Diagnostic contains:",
" return toString()::equals;",
" return v -> \"foo\".equals(v);",
" }",
"",
" Comparator<String> positive9() {",
" A positive9() {",
" // BUG: Diagnostic contains:",
" return toString().CASE_INSENSITIVE_ORDER;",
" return this;",
" }",
"",
" String positive10() {",
" Predicate<String> positive10() {",
" // BUG: Diagnostic contains:",
" return (toString());",
" return \"foo\"::equals;",
" }",
"",
" Object positive11() {",
" A positive11() {",
" // BUG: Diagnostic contains:",
" return (Object) toString();",
" return (this);",
" }",
"",
" int positive12() {",
" Object positive12() {",
" // BUG: Diagnostic contains:",
" return -hashCode();",
" return (Object) this;",
" }",
"",
" boolean positive13() {",
" // BUG: Diagnostic contains:",
" return !false;",
" }",
"}")
.doTest();
}
/** A {@link BugChecker} that simply delegates to {@link RequiresComputation}. */
/** A {@link BugChecker} that simply delegates to {@link IsLikelyTrivialComputation}. */
@BugPattern(
summary = "Flags return statement expressions matched by `RequiresComputation`",
summary = "Flags return statement expressions matched by `IsLikelyTrivialComputation`",
severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;
@@ -151,7 +141,7 @@ final class RequiresComputationTest {
super(
(expressionTree, state) ->
state.getPath().getParentPath().getLeaf() instanceof ReturnTree
&& new RequiresComputation().matches(expressionTree, state));
&& new IsLikelyTrivialComputation().matches(expressionTree, state));
}
}
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.refaster.test;
import java.util.stream.Stream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
// XXX: Drop these annotations.
@Disabled
@SuppressWarnings("all")
abstract class MyAbstractTest {
@TestFactory
Stream<DynamicTest> dynamicTests() {
MatchInWrongMethodRules.class.getDeclaredClasses();
return Stream.of(
DynamicTest.dynamicTest("A " + getClass(), () -> {}),
DynamicTest.dynamicTest("A", () -> {}));
}
}

View File

@@ -0,0 +1,168 @@
package tech.picnic.errorprone.refaster.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstanceFactory;
import org.junit.jupiter.api.extension.TestInstanceFactoryContext;
import org.junit.jupiter.api.extension.TestInstantiationException;
// XXX: Drop these annotations.
@Disabled
@SuppressWarnings("all")
public class MyTest extends MyAbstractTest {
public static final class MyTestInstanceFactory implements TestInstanceFactory {
@Override
public Object createTestInstance(
TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext)
throws TestInstantiationException {
return new MySubTest();
}
}
// XXX: Name
@Test
void foo() {
// Options:
// 1. Add examples to the Refaster rule, generate the tests from those
//
// 2. Define as test, and use a custom test annotation, which:
// - Generates the relevant test resources
// - Executes them.
// ^ Might be nicer to generate a subclass and use a `TestInstanceFactory` to instantiate that.
// XXX: That turns out not to work.
//
// ^ Other option: provide super class that declares a `TestFactory` based on the subclass name.
}
// Extensions can be defined at any level. TBD how useful.
// static final class MyExtension implements Extension, {
//
// }
// @ExtendWith(MyExtension.class)
static class MySubTest extends MyTest {}
@TestFactory
Stream<DynamicTest> dynamicTestsFromStreamInJava8() {
Function<String, String> resolver = s -> s;
List<String> domainNames =
Arrays.asList("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com");
List<String> outputList = Arrays.asList("154.174.10.56", "211.152.104.132", "178.144.120.156");
return domainNames.stream()
.map(
dom ->
DynamicTest.dynamicTest(
"Resolving: " + dom,
() -> {
int id = domainNames.indexOf(dom);
assertEquals(outputList.get(id), resolver.apply(dom));
}));
}
static final class PrimitiveOrReferenceEquality
implements ExpressionTestCase<ImmutableSet<Boolean>> {
public ImmutableSet<Boolean> before() {
return ImmutableSet.of(
RoundingMode.UP.equals(RoundingMode.DOWN),
Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
!RoundingMode.UP.equals(RoundingMode.DOWN),
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
}
public ImmutableSet<Boolean> after() {
return ImmutableSet.of(
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN);
}
}
// XXX: This variant can verify that the values are equal.
static final class PrimitiveOrReferenceEquality2 implements ExpressionTestCase2<Boolean> {
public void before(Consumer<Boolean> sink) {
sink.accept(RoundingMode.UP.equals(RoundingMode.DOWN));
sink.accept(Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
sink.accept(!RoundingMode.UP.equals(RoundingMode.DOWN));
sink.accept(!Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
}
public void after(Consumer<Boolean> sink) {
sink.accept(RoundingMode.UP == RoundingMode.DOWN);
sink.accept(RoundingMode.UP == RoundingMode.DOWN);
sink.accept(RoundingMode.UP != RoundingMode.DOWN);
sink.accept(RoundingMode.UP != RoundingMode.DOWN);
}
}
// With this setup we could validate equality, and (in theory) even report the exact source in
// case of an error.
// @Test
void primitiveOrReferenceEquality(BiConsumer<Boolean, Boolean> testCase) {
testCase.accept(
RoundingMode.UP.equals(RoundingMode.DOWN), RoundingMode.UP == RoundingMode.DOWN);
testCase.accept(
Objects.equals(RoundingMode.UP, RoundingMode.DOWN), RoundingMode.UP == RoundingMode.DOWN);
testCase.accept(
!RoundingMode.UP.equals(RoundingMode.DOWN), RoundingMode.UP != RoundingMode.DOWN);
testCase.accept(
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN), RoundingMode.UP != RoundingMode.DOWN);
}
// With this setup we could validate equality, and (in theory) even report the exact source in
// case of an error.
// @Test
void primitiveOrReferenceEquality2(BiConsumer<Supplier<Boolean>, Supplier<Boolean>> testCase) {
testCase.accept(
() -> RoundingMode.UP.equals(RoundingMode.DOWN),
() -> RoundingMode.UP == RoundingMode.DOWN);
testCase.accept(
() -> Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
() -> RoundingMode.UP == RoundingMode.DOWN);
testCase.accept(
() -> !RoundingMode.UP.equals(RoundingMode.DOWN),
() -> RoundingMode.UP != RoundingMode.DOWN);
testCase.accept(
() -> !Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
() -> RoundingMode.UP != RoundingMode.DOWN);
}
interface ExpressionTestCase<T> {
T before();
T after();
}
interface ExpressionTestCase2<T> {
void before(Consumer<T> sink);
void after(Consumer<T> sink);
}
// record ExpressionTestCaseX<T>(Supplier<T> before, Supplier<T> after);
}

View File

@@ -4,7 +4,6 @@ releases:
- version: 0.18.0
compatible:
- "2.30.0"
- "2.31.0"
- version: 0.17.0
compatible:
- "2.29.2"