Compare commits

...

8 Commits

Author SHA1 Message Date
Rick Ossendrijver
e952fc94c7 PrimitiveComparison: Add type arguments if needed 2022-02-14 10:27:48 +01:00
Rick Ossendrijver
c8c896c9e4 StaticImport: Add ZoneOffset.UTC and add template to TimeTemplates 2022-02-14 10:27:47 +01:00
Rick Ossendrijver
3ba8a83cd1 JUnitMethodDecl: emit warning instead of SuggestedFix if name clashes 2022-02-14 10:27:47 +01:00
Rick Ossendrijver
0824a5254b Introduce IdentityConversionCheck 2022-02-14 10:27:40 +01:00
Rick Ossendrijver
72a8bf7e12 Add type information in aftertemplates for builders 2022-02-07 10:51:49 +01:00
Rick Ossendrijver
427435b3f1 Introduce IsIdentity Matcher and use it in ImmutableMapTemplates
The Matcher is used for `Maps#toMap` and `Maps#UniqueIndex`.
2022-02-07 10:51:49 +01:00
Rick Ossendrijver
432ce9bce0 Drop some candidates from AssertJBigDecimalTemplates and explanation 2022-02-07 10:51:49 +01:00
Rick Ossendrijver
19da6ac149 Add type argument in @AfterTemplate of ImmutableListBuilder template 2022-02-07 10:51:48 +01:00
41 changed files with 825 additions and 257 deletions

View File

@@ -0,0 +1,91 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.List;
/** A {@link BugChecker} that flags redundant identity conversions. */
@AutoService(BugChecker.class)
@BugPattern(
name = "IdentityConversion",
summary = "Avoid or clarify identity conversions",
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.SIMPLIFICATION)
public final class IdentityConversionCheck extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableListMultimap",
"com.google.common.collect.ImmutableMap",
"com.google.common.collect.ImmutableMultimap",
"com.google.common.collect.ImmutableMultiset",
"com.google.common.collect.ImmutableRangeMap",
"com.google.common.collect.ImmutableRangeSet",
"com.google.common.collect.ImmutableSet",
"com.google.common.collect.ImmutableSetMultimap",
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClassAny(
Byte.class.getName(),
Character.class.getName(),
Double.class.getName(),
Float.class.getName(),
Integer.class.getName(),
String.class.getName())
.named("valueOf"),
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
staticMethod()
.onClass("reactor.core.publisher.Flux")
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() != 1 || !IS_CONVERSION_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree sourceTree = arguments.get(0);
Type sourceType = ASTHelpers.getType(sourceTree);
TargetType targetType = ASTHelpers.targetType(state);
if (sourceType == null
|| targetType == null
|| !state.getTypes().isSubtype(sourceType, targetType.type())) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
"This method invocation appears redundant; remove it or suppress this warning and "
+ "add an comment explaining its purpose")
.addFix(SuggestedFix.replace(tree, state.getSourceForNode(sourceTree)))
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
.build();
}
}

View File

@@ -20,10 +20,16 @@ import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import java.util.Objects;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check which enforces that test classes:
@@ -78,24 +84,83 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
.ifPresent(builder::merge);
if (isTestMethod) {
// XXX: In theory this rename could clash with an existing method or static import. In that
// case we should emit a warning without a suggested replacement.
tryCanonicalizeMethodName(tree, state).ifPresent(builder::merge);
}
Optional<String> newMethodName = tryCanonicalizeMethodName(tree);
if (newMethodName.isEmpty()) {
return describeMatchesIfPresent(tree, builder);
}
boolean reportedNameClash =
reportDescriptionForPossibleNameClash(tree, newMethodName.orElseThrow(), state);
if (!reportedNameClash) {
builder.merge(SuggestedFixes.renameMethod(tree, newMethodName.orElseThrow(), state));
}
}
return describeMatchesIfPresent(tree, builder);
}
private Description describeMatchesIfPresent(MethodTree tree, SuggestedFix.Builder builder) {
return builder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, builder.build());
}
private static Optional<SuggestedFix> tryCanonicalizeMethodName(
MethodTree tree, VisitorState state) {
private boolean reportDescriptionForPossibleNameClash(
MethodTree tree, String methodName, VisitorState state) {
if (isMethodNameInClass(methodName, state)) {
state.reportMatch(
buildDescription(tree)
.setMessage(
String.format("A method with name %s already exists in the class.", methodName))
.build());
return true;
}
if (isMethodNameStaticallyImported(methodName, state)) {
state.reportMatch(
buildDescription(tree)
.setMessage(
String.format(
"A method with name %s is already statically imported.", methodName))
.build());
return true;
}
return false;
}
private static boolean isMethodNameInClass(String methodName, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(member -> !ASTHelpers.isGeneratedConstructor(member))
.map(MethodTree::getName)
.map(Name::toString)
.anyMatch(methodName::equals);
}
private static boolean isMethodNameStaticallyImported(String methodName, VisitorState state) {
CompilationUnitTree compilationUnit = state.getPath().getCompilationUnit();
return compilationUnit.getImports().stream()
.filter(Objects::nonNull)
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportIdentifier(tree, state))
.anyMatch(methodName::contentEquals);
}
private static CharSequence getStaticImportIdentifier(Tree tree, VisitorState state) {
String source = Util.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(sym -> sym.getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))
.filter(not(String::isEmpty))
.map(name -> Character.toLowerCase(name.charAt(0)) + name.substring(1))
.filter(name -> !Character.isDigit(name.charAt(0)))
.map(name -> SuggestedFixes.renameMethod(tree, name, state));
.filter(name -> !Character.isDigit(name.charAt(0)));
}
// XXX: Move to a `MoreMatchers` utility class.

View File

@@ -37,7 +37,6 @@ import java.util.function.Function;
*/
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more
// specific types.
// XXX: Change this checker's name?
@AutoService(BugChecker.class)
@BugPattern(
name = "PrimitiveComparison",
@@ -77,23 +76,44 @@ public final class PrimitiveComparisonCheck extends BugChecker
}
return getPotentiallyBoxedReturnType(tree.getArguments().get(0))
.flatMap(cmpType -> tryFix(tree, state, cmpType, isStatic))
.flatMap(cmpType -> tryMakeMethodCallMorePrecise(tree, cmpType, isStatic, state))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional<Fix> tryFix(
MethodInvocationTree tree, VisitorState state, Type cmpType, boolean isStatic) {
private static Optional<Fix> tryMakeMethodCallMorePrecise(
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(methodSymbol -> methodSymbol.getSimpleName().toString())
.flatMap(
actualMethodName ->
Optional.of(getPreferredMethod(state, cmpType, isStatic))
Optional.of(getPreferredMethod(cmpType, isStatic, state))
.filter(not(actualMethodName::equals)))
.map(
preferredMethodName ->
prefixWithTypeArgumentsIfNeeded(preferredMethodName, tree, cmpType, state))
.map(preferredMethodName -> suggestFix(tree, preferredMethodName, state));
}
private static String getPreferredMethod(VisitorState state, Type cmpType, boolean isStatic) {
private static String prefixWithTypeArgumentsIfNeeded(
String preferredMethodName, MethodInvocationTree tree, Type cmpType, VisitorState state) {
int typeArguments = tree.getTypeArguments().size();
boolean methodNameIsComparing = preferredMethodName.equals("comparing");
if (typeArguments == 0 || (typeArguments == 1 && !methodNameIsComparing)) {
return preferredMethodName;
}
String optionalSecondTypeArgument =
methodNameIsComparing ? ", " + cmpType.tsym.getSimpleName() : "";
return String.format(
"<%s%s>%s",
Util.treeToString(tree.getTypeArguments().get(0), state),
optionalSecondTypeArgument,
preferredMethodName);
}
private static String getPreferredMethod(Type cmpType, boolean isStatic, VisitorState state) {
Types types = state.getTypes();
Symtab symtab = state.getSymtab();
@@ -128,9 +148,6 @@ public final class PrimitiveComparisonCheck extends BugChecker
}
}
// XXX: We drop explicitly specified generic type information. In case the number of type
// arguments before and after doesn't match, that's for the better. But if we e.g. replace
// `comparingLong` with `comparingInt`, then we should retain it.
private static Fix suggestFix(
MethodInvocationTree tree, String preferredMethodName, VisitorState state) {
ExpressionTree expr = tree.getMethodSelect();

View File

@@ -119,6 +119,7 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
.put("com.google.common.collect.ImmutableTable", "toImmutableTable")
.put("com.google.common.collect.Sets", "toImmutableEnumSet")
.put("com.google.common.base.Functions", "identity")
.put("java.time.ZoneOffset", "UTC")
.put("java.util.function.Function", "identity")
.put("java.util.function.Predicate", "not")
.put("org.junit.jupiter.params.provider.Arguments", "arguments")

View File

@@ -8,7 +8,22 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractBigDecimalAssert;
import org.assertj.core.api.BigDecimalAssert;
/**
* Refaster templates which improve {@link BigDecimal} asserts written in AssertJ.
*
* <p>A few {@link BigDecimal} assertions are not rewritten. This is because the {@link BigDecimal}
* also uses scale to determine whether two values are equal. As a result, we cannot rewrite the
* following assertions:
*
* <ul>
* <li>{@link BigDecimalAssert#isEqualTo(Object)} (for values 0L, 1L, BigDecimal.ZERO, and
* BigDecimal.ONE)
* <li>{@link BigDecimalAssert#isNotEqualTo(Object)} (for values 0L, 1L, BigDecimal.ZERO, and
* BigDecimal.ONE)
* </ul>
*/
// XXX: If we add a rule which drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
// cases below can go.
final class AssertJBigDecimalTemplates {
@@ -45,45 +60,36 @@ final class AssertJBigDecimalTemplates {
static final class AbstractBigDecimalAssertIsZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isZero(),
bigDecimalAssert.isEqualTo(0L),
bigDecimalAssert.isEqualTo(BigDecimal.ZERO));
return bigDecimalAssert.isEqualTo(BigDecimal.ZERO);
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(0);
return bigDecimalAssert.isZero();
}
}
static final class AbstractBigDecimalAssertIsNotZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isNotZero(),
bigDecimalAssert.isNotEqualTo(0L),
bigDecimalAssert.isNotEqualTo(BigDecimal.ZERO));
return bigDecimalAssert.isNotEqualTo(BigDecimal.ZERO);
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isNotEqualTo(0);
return bigDecimalAssert.isNotZero();
}
}
static final class AbstractBigDecimalAssertIsOne {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isOne(),
bigDecimalAssert.isEqualTo(1L),
bigDecimalAssert.isEqualTo(BigDecimal.ONE));
return bigDecimalAssert.isEqualTo(BigDecimal.ONE);
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(1);
return bigDecimalAssert.isOne();
}
}
}

View File

@@ -177,9 +177,7 @@ final class AssortedTemplates {
@BeforeTemplate
boolean before(Collection<T> collection1, Collection<T> collection2) {
return Refaster.anyOf(
Collections.disjoint(ImmutableSet.copyOf(collection1), collection2),
Collections.disjoint(new HashSet<>(collection1), collection2),
Collections.disjoint(collection1, ImmutableSet.copyOf(collection2)),
Collections.disjoint(collection1, new HashSet<>(collection2)));
}

View File

@@ -272,17 +272,4 @@ final class ImmutableListMultimapTemplates {
return ImmutableListMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableListMultimap}. */
static final class ImmutableListMultimapCopyOfImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableListMultimap<K, V> before(ImmutableListMultimap<K, V> multimap) {
return ImmutableListMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableListMultimap<K, V> after(ImmutableListMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -37,7 +37,7 @@ final class ImmutableListTemplates {
@AfterTemplate
ImmutableList.Builder<T> after() {
return ImmutableList.builder();
return ImmutableList.<T>builder();
}
}

View File

@@ -10,7 +10,9 @@ import com.google.errorprone.refaster.ImportPolicy;
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;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Collection;
@@ -20,6 +22,7 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.util.IsIdentity;
/** Refaster templates related to expressions dealing with {@link ImmutableMap}s. */
final class ImmutableMapTemplates {
@@ -36,7 +39,7 @@ final class ImmutableMapTemplates {
@AfterTemplate
ImmutableMap.Builder<K, V> after() {
return ImmutableMap.builder();
return ImmutableMap.<K, V>builder();
}
}
@@ -94,32 +97,35 @@ final class ImmutableMapTemplates {
* Prefer {@link Maps#toMap(Iterable, com.google.common.base.Function)} over more contrived
* alternatives.
*/
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
static final class IterableToImmutableMap<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(
Iterator<K> iterable, Function<? super K, ? extends V> valueFunction) {
Iterator<K> iterable,
@NotMatches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Iterable<K> iterable, Function<? super K, ? extends V> valueFunction) {
Iterable<K> iterable,
@NotMatches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Collection<K> iterable, Function<? super K, ? extends V> valueFunction) {
Collection<K> iterable,
@NotMatches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return iterable.stream()
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Set<K> iterable, com.google.common.base.Function<? super K, V> valueFunction) {
Set<K> iterable,
@NotMatches(IsIdentity.class) com.google.common.base.Function<? super K, V> valueFunction) {
return ImmutableMap.copyOf(Maps.asMap(iterable, valueFunction));
}
@@ -130,6 +136,49 @@ final class ImmutableMapTemplates {
}
}
/**
* Prefer {@link Maps#toMap(Iterable, com.google.common.base.Function)} over more contrived
* alternatives and rewrite {@link Function#identity()} to {@code k -> k}.
*/
static final class IterableToImmutableMapIdentity<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(
Iterator<K> iterable,
@Matches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Iterable<K> iterable,
@Matches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Collection<K> iterable,
@Matches(IsIdentity.class) Function<? super K, ? extends V> valueFunction) {
return iterable.stream()
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Set<K> iterable,
@Matches(IsIdentity.class) com.google.common.base.Function<? super K, V> valueFunction) {
return ImmutableMap.copyOf(Maps.asMap(iterable, valueFunction));
}
@AfterTemplate
ImmutableMap<K, K> after(
Iterable<K> iterable, com.google.common.base.Function<? super K, V> valueFunction) {
return Maps.toMap(iterable, k -> k);
}
}
/** Prefer {@link ImmutableMap#copyOf(Iterable)} over more contrived alternatives. */
static final class EntryIterableToImmutableMap<K, V> {
@BeforeTemplate
@@ -188,23 +237,27 @@ final class ImmutableMapTemplates {
* Prefer {@link Maps#uniqueIndex(Iterable, com.google.common.base.Function)} over the
* stream-based alternative.
*/
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
static final class IndexIterableToImmutableMap<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(Iterator<V> iterable, Function<? super V, ? extends K> keyFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@BeforeTemplate
ImmutableMap<K, V> before(Iterable<V> iterable, Function<? super V, ? extends K> keyFunction) {
ImmutableMap<K, V> before(
Iterator<V> iterable,
@NotMatches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Collection<V> iterable, Function<? super V, ? extends K> keyFunction) {
Iterable<V> iterable,
@NotMatches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Collection<V> iterable,
@NotMatches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return iterable.stream()
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@@ -216,6 +269,42 @@ final class ImmutableMapTemplates {
}
}
/**
* Prefer {@link Maps#uniqueIndex(Iterable, com.google.common.base.Function)} over the
* stream-based alternatives and rewrite {@link Function#identity()} to {@code k -> k}.
*/
static final class IndexIterableToImmutableMapIdentity<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(
Iterator<V> iterable,
@Matches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Iterable<V> iterable,
@Matches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return Streams.stream(iterable)
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@BeforeTemplate
ImmutableMap<K, V> before(
Collection<V> iterable,
@Matches(IsIdentity.class) Function<? super V, ? extends K> keyFunction) {
return iterable.stream()
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
}
@AfterTemplate
ImmutableMap<V, V> after(
Iterable<V> iterable, com.google.common.base.Function<? super V, K> keyFunction) {
return Maps.uniqueIndex(iterable, v -> v);
}
}
/**
* Prefer creating an immutable copy of the result of {@link Maps#transformValues(Map,
* com.google.common.base.Function)} over more contrived alternatives.
@@ -242,19 +331,6 @@ final class ImmutableMapTemplates {
}
}
/** Don't unnecessarily copy an {@link ImmutableMap}. */
static final class ImmutableMapCopyOfImmutableMap<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(map);
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return map;
}
}
// XXX: Add a template for this:
// Maps.transformValues(streamOfEntries.collect(groupBy(fun)), ImmutableMap::copyOf)
// ->

View File

@@ -101,17 +101,4 @@ final class ImmutableMultisetTemplates {
return stream.collect(toImmutableMultiset());
}
}
/** Don't unnecessarily copy an {@link ImmutableMultiset}. */
static final class ImmutableMultisetCopyOfImmutableMultiset<T> {
@BeforeTemplate
ImmutableMultiset<T> before(ImmutableMultiset<T> multiset) {
return ImmutableMultiset.copyOf(multiset);
}
@AfterTemplate
ImmutableMultiset<T> after(ImmutableMultiset<T> multiset) {
return multiset;
}
}
}

View File

@@ -37,7 +37,7 @@ final class ImmutableSetMultimapTemplates {
@AfterTemplate
ImmutableSetMultimap.Builder<K, V> after() {
return ImmutableSetMultimap.builder();
return ImmutableSetMultimap.<K, V>builder();
}
}
@@ -215,17 +215,4 @@ final class ImmutableSetMultimapTemplates {
return ImmutableSetMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableSetMultimap}. */
static final class ImmutableSetMultimapCopyOfImmutableSetMultimap<K, V> {
@BeforeTemplate
ImmutableSetMultimap<K, V> before(ImmutableSetMultimap<K, V> multimap) {
return ImmutableSetMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableSetMultimap<K, V> after(ImmutableSetMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -35,7 +35,7 @@ final class ImmutableSetTemplates {
@AfterTemplate
ImmutableSet.Builder<T> after() {
return ImmutableSet.builder();
return ImmutableSet.<T>builder();
}
}
@@ -126,19 +126,6 @@ final class ImmutableSetTemplates {
}
}
/** Don't unnecessarily copy an {@link ImmutableSet}. */
static final class ImmutableSetCopyOfImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before(ImmutableSet<T> set) {
return ImmutableSet.copyOf(set);
}
@AfterTemplate
ImmutableSet<T> after(ImmutableSet<T> set) {
return set;
}
}
/** Prefer {@link SetView#immutableCopy()} over the more verbose alternative. */
static final class ImmutableSetCopyOfSetView<T> {
@BeforeTemplate

View File

@@ -203,19 +203,6 @@ final class ReactorTemplates {
}
}
/** Don't unnecessarily invoke {@link Flux#concat(Publisher)}. */
static final class FluxIdentity<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux) {
return Flux.concat(flux);
}
@AfterTemplate
Flux<T> after(Flux<T> flux) {
return flux;
}
}
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/

View File

@@ -41,11 +41,9 @@ final class RxJava2AdapterTemplates {
@BeforeTemplate
Publisher<T> before(Flowable<T> flowable) {
return Refaster.anyOf(
Flux.from(flowable),
flowable.compose(Flux::from),
flowable.to(Flux::from),
flowable.as(Flux::from),
RxJava2Adapter.flowableToFlux(flowable),
flowable.compose(RxJava2Adapter::flowableToFlux),
flowable.to(RxJava2Adapter::flowableToFlux));
}
@@ -67,7 +65,6 @@ final class RxJava2AdapterTemplates {
Flowable.fromPublisher(flux),
flux.transform(Flowable::fromPublisher),
flux.as(Flowable::fromPublisher),
RxJava2Adapter.fluxToFlowable(flux),
flux.transform(RxJava2Adapter::fluxToFlowable));
}
@@ -140,7 +137,6 @@ final class RxJava2AdapterTemplates {
Flowable.fromPublisher(mono),
mono.transform(Flowable::fromPublisher),
mono.as(Flowable::fromPublisher),
RxJava2Adapter.monoToFlowable(mono),
mono.transform(RxJava2Adapter::monoToFlowable));
}

View File

@@ -59,6 +59,19 @@ final class TimeTemplates {
}
}
/** Prefer {@link Instant#atOffset(ZoneOffset)} over the more verbose alternative. */
static final class InstantAtOffset {
@BeforeTemplate
OffsetDateTime before(Instant instant, ZoneOffset zoneOffset) {
return OffsetDateTime.ofInstant(instant, zoneOffset);
}
@AfterTemplate
OffsetDateTime after(Instant instant, ZoneOffset zoneOffset) {
return instant.atOffset(zoneOffset);
}
}
/** Use {@link Clock#systemUTC()} when possible. */
static final class UtcClock {
@BeforeTemplate

View File

@@ -0,0 +1,234 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IdentityConversionCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IdentityConversionCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IdentityConversionCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"Foo.java",
"import com.google.common.collect.ImmutableBiMap;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableListMultimap;",
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableMultimap;",
"import com.google.common.collect.ImmutableMultiset;",
"import com.google.common.collect.ImmutableRangeMap;",
"import com.google.common.collect.ImmutableRangeSet;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSetMultimap;",
"import com.google.common.collect.ImmutableTable;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
" // BUG: Diagnostic contains:",
" Byte b1 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" Byte b2 = Byte.valueOf(Byte.MIN_VALUE);",
" byte b3 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" byte b4 = Byte.valueOf(Byte.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" Character c1 = Character.valueOf((Character) 'a');",
" Character c2 = Character.valueOf('a');",
" char c3 = Character.valueOf((Character)'a');",
" // BUG: Diagnostic contains:",
" char c4 = Character.valueOf('a');",
"",
" // BUG: Diagnostic contains:",
" Integer int1 = Integer.valueOf((Integer) 1);",
" Integer int2 = Integer.valueOf(1);",
" int int3 = Integer.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" int int4 = Integer.valueOf(1);",
"",
" String s1 = String.valueOf(0);",
" // BUG: Diagnostic contains:",
" String s2 = String.valueOf(\"1\");",
"",
" // BUG: Diagnostic contains:",
" ImmutableBiMap<Object, Object> i2 = ImmutableBiMap.copyOf(ImmutableBiMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableList<Object> i3 = ImmutableList.copyOf(ImmutableList.of());",
" // BUG: Diagnostic contains:",
" ImmutableListMultimap<Object, Object> i4 = ImmutableListMultimap.copyOf(ImmutableListMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMap<Object, Object> i5 = ImmutableMap.copyOf(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultimap<Object, Object> i6 = ImmutableMultimap.copyOf(ImmutableMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultiset<Object> i7 = ImmutableMultiset.copyOf(ImmutableMultiset.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeMap<String, Object> i8 = ImmutableRangeMap.copyOf(ImmutableRangeMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeSet<String> i9 = ImmutableRangeSet.copyOf(ImmutableRangeSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSet<Object> i10 = ImmutableSet.copyOf(ImmutableSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSetMultimap<Object, Object> i11 = ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableTable<Object, Object, Object> i15 = ImmutableTable.copyOf(ImmutableTable.of());",
"",
" // BUG: Diagnostic contains:",
" Flux<Integer> f1 = Flux.just(1).flatMap(e -> RxJava2Adapter.fluxToFlowable(Flux.just(2)));",
" // BUG: Diagnostic contains:",
" Flux<Integer> f2 = Flux.concat(Flux.just(1));",
" // BUG: Diagnostic contains:",
" Flux<Integer> f3 = Flux.firstWithSignal(Flux.just(1));",
" // BUG: Diagnostic contains:",
" Flux<Integer> f4 = Flux.from(Flux.just(1));",
" // BUG: Diagnostic contains:",
" Flux<Integer> f5 = Flux.merge(Flux.just(1));",
"",
" // BUG: Diagnostic contains:",
" Mono<Integer> m1 = Mono.from(Mono.just(1));",
" // BUG: Diagnostic contains:",
" Mono<Integer> m2 = Mono.fromDirect(Mono.just(1));",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"Foo.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.Collection;",
"import java.util.ArrayList;",
"import org.reactivestreams.Publisher;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.copyOf(ImmutableList.of(1));",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.copyOf(ImmutableSet.of(1));",
" Collection<Integer> c2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Flux<Integer> f1 = Flux.just(1).flatMap(e -> RxJava2Adapter.fluxToFlowable(Flux.just(2)));",
" Flux<Integer> f2 = Flux.concat(Flux.just(3));",
" Publisher<Integer> f3 = Flux.firstWithSignal(Flux.just(4));",
" Publisher<Integer> f4 = Flux.from(Flux.just(5));",
" Publisher<Integer> f5 = Flux.merge(Flux.just(6));",
"",
" Mono<Integer> m1 = Mono.from(Mono.just(7));",
" Publisher<Integer> m2 = Mono.fromDirect(Mono.just(8));",
"",
" bar(Flux.concat(Flux.just(9)));",
" bar(Mono.from(Mono.just(10)));",
" }",
"",
" void bar(Publisher<Integer> publisher) {}",
"}")
.addOutputLines(
"Foo.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.Collection;",
"import java.util.ArrayList;",
"import org.reactivestreams.Publisher;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
" ImmutableSet<Object> set1 = ImmutableSet.of();",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.of(1);",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.of(1);",
" Collection<Integer> c2 = new ArrayList<>(ImmutableList.of(1));",
"",
" Flux<Integer> f1 = Flux.just(1).flatMap(e -> Flux.just(2));",
" Flux<Integer> f2 = Flux.just(3);",
" Publisher<Integer> f3 = Flux.just(4);",
" Publisher<Integer> f4 = Flux.just(5);",
" Publisher<Integer> f5 = Flux.just(6);",
"",
" Mono<Integer> m1 = Mono.just(7);",
" Publisher<Integer> m2 = Mono.just(8);",
"",
" bar(Flux.just(9));",
" bar(Mono.just(10));",
" }",
"",
" void bar(Publisher<Integer> publisher) {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"Foo.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.copyOf(ImmutableList.of(1));",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
" }",
"}")
.addOutputLines(
"Foo.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
" @SuppressWarnings(\"IdentityConversion\")",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" @SuppressWarnings(\"IdentityConversion\")",
" ImmutableCollection<Integer> list1 = ImmutableList.copyOf(ImmutableList.of(1));",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -177,4 +177,94 @@ public final class JUnitMethodDeclarationCheckTest {
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void methodAlreadyExistsInClass() {
refactoringTestHelper
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test void testFoo() {}",
" void foo() {}",
"",
" @Test void testBar() {}",
" private void bar() {}",
"",
" @Test void testFooDifferent() {}",
" @Test void testBarDifferent() {}",
"}")
.addOutputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test void testFoo() {}",
" void foo() {}",
"",
" @Test void testBar() {}",
" private void bar() {}",
"",
" @Test void fooDifferent() {}",
" @Test void barDifferent() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void methodAlreadyInStaticImports() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.Test;",
"import com.google.common.collect.ImmutableSet;",
"",
"class A {",
" @Test",
" void testArguments() {",
" arguments(1, 2, 3);",
" }",
"",
" @Test",
" void testToImmutableSet() {",
" ImmutableSet.of(1).stream().filter(i -> i > 1).collect(toImmutableSet());",
" }",
"",
" @Test",
" void testArgumentsDifferentName() {}",
"",
" @Test",
" void testToImmutableSetDifferentName() {}",
"}")
.addOutputLines(
"A.java",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.Test;",
"import com.google.common.collect.ImmutableSet;",
"",
"class A {",
" @Test",
" void testArguments() {",
" arguments(1, 2, 3);",
" }",
"",
" @Test",
" void testToImmutableSet() {",
" ImmutableSet.of(1).stream().filter(i -> i > 1).collect(toImmutableSet());",
" }",
"",
" @Test",
" void argumentsDifferentName() {}",
"",
" @Test",
" void toImmutableSetDifferentName() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -426,8 +426,6 @@ public final class PrimitiveComparisonCheckTest {
.doTest();
}
// XXX: If the explicit `<A, BoxedPrimitive>` generic type information was necessary, then this
// replacement drops too much information.
@Test
void replacementWithPrimitiveVariants() {
refactoringTestHelper
@@ -459,13 +457,13 @@ public final class PrimitiveComparisonCheckTest {
"import java.util.Comparator;",
"",
"interface A extends Comparable<A> {",
" Comparator<A> bCmp = Comparator.comparingInt(o -> (byte) 0);",
" Comparator<A> cCmp = Comparator.comparingInt(o -> (char) 0);",
" Comparator<A> sCmp = Comparator.comparingInt(o -> (short) 0);",
" Comparator<A> iCmp = Comparator.comparingInt(o -> 0);",
" Comparator<A> lCmp = Comparator.comparingLong(o -> 0L);",
" Comparator<A> fCmp = Comparator.comparingDouble(o -> 0.0f);",
" Comparator<A> dCmp = Comparator.comparingDouble(o -> 0.0);",
" Comparator<A> bCmp = Comparator.<A>comparingInt(o -> (byte) 0);",
" Comparator<A> cCmp = Comparator.<A>comparingInt(o -> (char) 0);",
" Comparator<A> sCmp = Comparator.<A>comparingInt(o -> (short) 0);",
" Comparator<A> iCmp = Comparator.<A>comparingInt(o -> 0);",
" Comparator<A> lCmp = Comparator.<A>comparingLong(o -> 0L);",
" Comparator<A> fCmp = Comparator.<A>comparingDouble(o -> 0.0f);",
" Comparator<A> dCmp = Comparator.<A>comparingDouble(o -> 0.0);",
"",
" default void m() {",
" bCmp.thenComparingInt(o -> (byte) 0);",
@@ -480,8 +478,6 @@ public final class PrimitiveComparisonCheckTest {
.doTest(TestMode.TEXT_MATCH);
}
// XXX: If the explicit `<A>` generic type information was necessary, then this replacement drops
// too much information.
@Test
void replacementWithBoxedVariants() {
refactoringTestHelper
@@ -513,13 +509,13 @@ public final class PrimitiveComparisonCheckTest {
"import java.util.Comparator;",
"",
"interface A extends Comparable<A> {",
" Comparator<A> bCmp = Comparator.comparing(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp = Comparator.comparing(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp = Comparator.comparing(o -> Short.valueOf((short) 0));",
" Comparator<A> iCmp = Comparator.comparing(o -> Integer.valueOf(0));",
" Comparator<A> lCmp = Comparator.comparing(o -> Long.valueOf(0));",
" Comparator<A> fCmp = Comparator.comparing(o -> Float.valueOf(0));",
" Comparator<A> dCmp = Comparator.comparing(o -> Double.valueOf(0));",
" Comparator<A> bCmp = Comparator.<A, Byte>comparing(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp = Comparator.<A, Character>comparing(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp = Comparator.<A, Short>comparing(o -> Short.valueOf((short) 0));",
" Comparator<A> iCmp = Comparator.<A, Integer>comparing(o -> Integer.valueOf(0));",
" Comparator<A> lCmp = Comparator.<A, Long>comparing(o -> Long.valueOf(0));",
" Comparator<A> fCmp = Comparator.<A, Float>comparing(o -> Float.valueOf(0));",
" Comparator<A> dCmp = Comparator.<A, Double>comparing(o -> Double.valueOf(0));",
"",
" default void m() {",
" bCmp.thenComparing(o -> Byte.valueOf((byte) 0));",

View File

@@ -113,6 +113,7 @@ public final class StaticImportCheckTest {
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableSet;",
"import java.nio.charset.StandardCharsets;",
"import java.time.ZoneOffset;",
"import java.util.Objects;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.springframework.format.annotation.DateTimeFormat;",
@@ -143,6 +144,9 @@ public final class StaticImportCheckTest {
" MediaType.APPLICATION_XHTML_XML,",
" MediaType.TEXT_HTML,",
" MediaType.valueOf(\"image/webp\"));",
"",
" ZoneOffset z1 = ZoneOffset.UTC;",
" ZoneOffset z2 = ZoneOffset.MIN;",
" }",
"",
" void m2(",
@@ -163,6 +167,7 @@ public final class StaticImportCheckTest {
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.nio.charset.StandardCharsets.UTF_8;",
"import static java.time.ZoneOffset.UTC;",
"import static java.util.Objects.requireNonNull;",
"import static java.util.function.Predicate.not;",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
@@ -177,6 +182,7 @@ public final class StaticImportCheckTest {
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableSet;",
"import java.nio.charset.StandardCharsets;",
"import java.time.ZoneOffset;",
"import java.util.Objects;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.springframework.boot.test.context.SpringBootTest;",
@@ -207,6 +213,9 @@ public final class StaticImportCheckTest {
" APPLICATION_XHTML_XML,",
" TEXT_HTML,",
" MediaType.valueOf(\"image/webp\"));",
"",
" ZoneOffset z1 = UTC;",
" ZoneOffset z2 = ZoneOffset.MIN;",
" }",
"",
" void m2(",

View File

@@ -26,24 +26,15 @@ final class AssertJBigDecimalTemplatesTest implements RefasterTemplateTestCase {
assertThat(BigDecimal.ZERO).isNotCloseTo(BigDecimal.ONE, withPercentage(0)));
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsZero() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isZero(),
assertThat(BigDecimal.ZERO).isEqualTo(0L),
assertThat(BigDecimal.ZERO).isEqualTo(BigDecimal.ZERO));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsZero() {
return assertThat(BigDecimal.ZERO).isEqualTo(BigDecimal.ZERO);
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsNotZero() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isNotZero(),
assertThat(BigDecimal.ZERO).isNotEqualTo(0L),
assertThat(BigDecimal.ZERO).isNotEqualTo(BigDecimal.ZERO));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsNotZero() {
return assertThat(BigDecimal.ZERO).isNotEqualTo(BigDecimal.ZERO);
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsOne() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isOne(),
assertThat(BigDecimal.ZERO).isEqualTo(1L),
assertThat(BigDecimal.ZERO).isEqualTo(BigDecimal.ONE));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsOne() {
return assertThat(BigDecimal.ZERO).isEqualTo(BigDecimal.ONE);
}
}

View File

@@ -26,24 +26,15 @@ final class AssertJBigDecimalTemplatesTest implements RefasterTemplateTestCase {
assertThat(BigDecimal.ZERO).isNotEqualTo(BigDecimal.ONE));
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsZero() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isEqualTo(0),
assertThat(BigDecimal.ZERO).isEqualTo(0),
assertThat(BigDecimal.ZERO).isEqualTo(0));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsZero() {
return assertThat(BigDecimal.ZERO).isZero();
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsNotZero() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isNotEqualTo(0),
assertThat(BigDecimal.ZERO).isNotEqualTo(0),
assertThat(BigDecimal.ZERO).isNotEqualTo(0));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsNotZero() {
return assertThat(BigDecimal.ZERO).isNotZero();
}
ImmutableSet<AbstractBigDecimalAssert<?>> testAbstractBigDecimalAssertIsOne() {
return ImmutableSet.of(
assertThat(BigDecimal.ZERO).isEqualTo(1),
assertThat(BigDecimal.ZERO).isEqualTo(1),
assertThat(BigDecimal.ZERO).isEqualTo(1));
AbstractBigDecimalAssert<?> testAbstractBigDecimalAssertIsOne() {
return assertThat(BigDecimal.ZERO).isOne();
}
}

View File

@@ -78,10 +78,8 @@ final class AssortedTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testDisjointCollections() {
return ImmutableSet.of(
Collections.disjoint(ImmutableSet.copyOf(ImmutableList.of(1)), ImmutableList.of(2)),
Collections.disjoint(new HashSet<>(ImmutableList.of(3)), ImmutableList.of(4)),
Collections.disjoint(ImmutableList.of(5), ImmutableSet.copyOf(ImmutableList.of(6))),
Collections.disjoint(ImmutableList.of(7), new HashSet<>(ImmutableList.of(8))));
Collections.disjoint(new HashSet<>(ImmutableList.of(1)), ImmutableList.of(2)),
Collections.disjoint(ImmutableList.of(3), new HashSet<>(ImmutableList.of(4))));
}
boolean testIterableIsEmpty() {

View File

@@ -81,9 +81,7 @@ final class AssortedTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testDisjointCollections() {
return ImmutableSet.of(
Collections.disjoint(ImmutableList.of(1), ImmutableList.of(2)),
Collections.disjoint(ImmutableList.of(3), ImmutableList.of(4)),
Collections.disjoint(ImmutableList.of(5), ImmutableList.of(6)),
Collections.disjoint(ImmutableList.of(7), ImmutableList.of(8)));
Collections.disjoint(ImmutableList.of(3), ImmutableList.of(4)));
}
boolean testIterableIsEmpty() {

View File

@@ -113,8 +113,4 @@ final class ImmutableListMultimapTemplatesTest implements RefasterTemplateTestCa
flatteningToImmutableListMultimap(
Map.Entry::getKey, e -> e.getValue().stream().map(Math::toIntExact))));
}
ImmutableListMultimap<String, Integer> testImmutableListMultimapCopyOfImmutableListMultimap() {
return ImmutableListMultimap.copyOf(ImmutableListMultimap.of("foo", 1));
}
}

View File

@@ -89,8 +89,4 @@ final class ImmutableListMultimapTemplatesTest implements RefasterTemplateTestCa
ImmutableListMultimap.copyOf(
Multimaps.transformValues(TreeMultimap.<String, Long>create(), Math::toIntExact)));
}
ImmutableListMultimap<String, Integer> testImmutableListMultimapCopyOfImmutableListMultimap() {
return ImmutableListMultimap.of("foo", 1);
}
}

View File

@@ -40,6 +40,7 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
}
@SuppressWarnings({"IterableToImmutableMapIdentity", "IndexIterableToImmutableMapIdentity"})
ImmutableSet<ImmutableMap<Integer, Integer>> testIterableToImmutableMap() {
return ImmutableSet.of(
ImmutableList.of(1).stream().collect(toImmutableMap(identity(), n -> n * 2)),
@@ -47,7 +48,14 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
.collect(toImmutableMap(n -> n, Integer::valueOf)),
Streams.stream(ImmutableList.of(3).iterator())
.collect(toImmutableMap(identity(), n -> n.intValue())),
ImmutableMap.copyOf(Maps.asMap(ImmutableSet.of(4), Integer::valueOf)));
ImmutableMap.copyOf(Maps.asMap(ImmutableSet.of(4), Integer::valueOf)),
ImmutableList.of(1).stream().collect(toImmutableMap(identity(), identity())));
}
ImmutableSet<ImmutableMap<Integer, Integer>> testIterableToImmutableMapIdentity() {
return ImmutableSet.of(
ImmutableList.of(1).stream().collect(toImmutableMap(identity(), identity())),
Streams.stream(ImmutableList.of(2)::iterator).collect(toImmutableMap(n -> n, identity())));
}
ImmutableSet<ImmutableMap<String, Integer>> testEntryIterableToImmutableMap() {
@@ -78,6 +86,16 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
.collect(toImmutableMap(n -> n.intValue(), identity())));
}
@SuppressWarnings("IterableToImmutableMapIdentity")
ImmutableSet<ImmutableMap<Integer, Integer>> testIndexIterableToImmutableMapIdentity() {
return ImmutableSet.of(
ImmutableList.of(1).stream().collect(toImmutableMap(identity(), identity())),
Streams.stream(ImmutableList.of(2)::iterator)
.collect(toImmutableMap(identity(), identity())),
Streams.stream(ImmutableList.of(3).iterator())
.collect(toImmutableMap(identity(), identity())));
}
ImmutableSet<ImmutableMap<String, Integer>> testTransformMapValuesToImmutableMap() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1L).entrySet().stream()
@@ -86,8 +104,4 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
ImmutableMap.of("bar", 2L).keySet(),
k -> Math.toIntExact(ImmutableMap.of("bar", 2L).get(k))));
}
ImmutableMap<String, Integer> testImmutableMapCopyOfImmutableMap() {
return ImmutableMap.copyOf(ImmutableMap.of("foo", 1));
}
}

View File

@@ -37,12 +37,19 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
ImmutableMap.of(Map.entry("foo", 1).getKey(), Map.entry("foo", 1).getValue()));
}
@SuppressWarnings({"IterableToImmutableMapIdentity", "IndexIterableToImmutableMapIdentity"})
ImmutableSet<ImmutableMap<Integer, Integer>> testIterableToImmutableMap() {
return ImmutableSet.of(
Maps.toMap(ImmutableList.of(1), n -> n * 2),
Maps.toMap(ImmutableList.of(2)::iterator, Integer::valueOf),
Maps.toMap(ImmutableList.of(3).iterator(), n -> n.intValue()),
Maps.toMap(ImmutableSet.of(4), Integer::valueOf));
Maps.toMap(ImmutableSet.of(4), Integer::valueOf),
ImmutableList.of(1).stream().collect(toImmutableMap(identity(), identity())));
}
ImmutableSet<ImmutableMap<Integer, Integer>> testIterableToImmutableMapIdentity() {
return ImmutableSet.of(
Maps.toMap(ImmutableList.of(1), k -> k), Maps.toMap(ImmutableList.of(2)::iterator, n -> n));
}
ImmutableSet<ImmutableMap<String, Integer>> testEntryIterableToImmutableMap() {
@@ -65,6 +72,14 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
Maps.uniqueIndex(ImmutableList.of(3).iterator(), n -> n.intValue()));
}
@SuppressWarnings("IterableToImmutableMapIdentity")
ImmutableSet<ImmutableMap<Integer, Integer>> testIndexIterableToImmutableMapIdentity() {
return ImmutableSet.of(
Maps.uniqueIndex(ImmutableList.of(1), v -> v),
Maps.uniqueIndex(ImmutableList.of(2)::iterator, v -> v),
Maps.uniqueIndex(ImmutableList.of(3).iterator(), v -> v));
}
ImmutableSet<ImmutableMap<String, Integer>> testTransformMapValuesToImmutableMap() {
return ImmutableSet.of(
ImmutableMap.copyOf(
@@ -72,8 +87,4 @@ final class ImmutableMapTemplatesTest implements RefasterTemplateTestCase {
ImmutableMap.copyOf(
Maps.transformValues(ImmutableMap.of("bar", 2L), v -> Math.toIntExact(v))));
}
ImmutableMap<String, Integer> testImmutableMapCopyOfImmutableMap() {
return ImmutableMap.of("foo", 1);
}
}

View File

@@ -44,8 +44,4 @@ final class ImmutableMultisetTemplatesTest implements RefasterTemplateTestCase {
ImmutableMultiset.copyOf(Stream.of(1).iterator()),
Stream.of(2).collect(collectingAndThen(toList(), ImmutableMultiset::copyOf)));
}
ImmutableMultiset<Integer> testImmutableMultisetCopyOfImmutableMultiset() {
return ImmutableMultiset.copyOf(ImmutableMultiset.of(1, 2));
}
}

View File

@@ -41,8 +41,4 @@ final class ImmutableMultisetTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(
Stream.of(1).collect(toImmutableMultiset()), Stream.of(2).collect(toImmutableMultiset()));
}
ImmutableMultiset<Integer> testImmutableMultisetCopyOfImmutableMultiset() {
return ImmutableMultiset.of(1, 2);
}
}

View File

@@ -91,8 +91,4 @@ final class ImmutableSetMultimapTemplatesTest implements RefasterTemplateTestCas
flatteningToImmutableSetMultimap(
Map.Entry::getKey, e -> e.getValue().stream().map(Math::toIntExact))));
}
ImmutableSetMultimap<String, Integer> testImmutableSetMultimapCopyOfImmutableSetMultimap() {
return ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of("foo", 1));
}
}

View File

@@ -72,8 +72,4 @@ final class ImmutableSetMultimapTemplatesTest implements RefasterTemplateTestCas
ImmutableSetMultimap.copyOf(
Multimaps.transformValues(TreeMultimap.<String, Long>create(), Math::toIntExact)));
}
ImmutableSetMultimap<String, Integer> testImmutableSetMultimapCopyOfImmutableSetMultimap() {
return ImmutableSetMultimap.of("foo", 1);
}
}

View File

@@ -59,10 +59,6 @@ final class ImmutableSetTemplatesTest implements RefasterTemplateTestCase {
Stream.of(4).collect(collectingAndThen(toSet(), ImmutableSet::copyOf)));
}
ImmutableSet<Integer> testImmutableSetCopyOfImmutableSet() {
return ImmutableSet.copyOf(ImmutableSet.of(1, 2));
}
ImmutableSet<Integer> testImmutableSetCopyOfSetView() {
return ImmutableSet.copyOf(Sets.difference(ImmutableSet.of(1), ImmutableSet.of(2)));
}

View File

@@ -58,10 +58,6 @@ final class ImmutableSetTemplatesTest implements RefasterTemplateTestCase {
Stream.of(4).collect(toImmutableSet()));
}
ImmutableSet<Integer> testImmutableSetCopyOfImmutableSet() {
return ImmutableSet.of(1, 2);
}
ImmutableSet<Integer> testImmutableSetCopyOfSetView() {
return Sets.difference(ImmutableSet.of(1), ImmutableSet.of(2)).immutableCopy();
}

View File

@@ -63,10 +63,6 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return Flux.concat(Mono.just("foo"));
}
Flux<String> testFluxIdentity() {
return Flux.concat(Flux.just("foo"));
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty()),

View File

@@ -63,10 +63,6 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return Mono.just("foo").flux();
}
Flux<String> testFluxIdentity() {
return Flux.just("foo");
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").flux().collect(toOptional()),

View File

@@ -25,13 +25,11 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
// seventh parameter onwards.
return ImmutableSet.copyOf(
Arrays.asList(
Flux.from(Flowable.just(1)),
Flowable.just(2).compose(Flux::from),
Flowable.just(3).to(Flux::from),
Flowable.just(4).as(Flux::from),
RxJava2Adapter.flowableToFlux(Flowable.just(5)),
Flowable.just(6).compose(RxJava2Adapter::flowableToFlux),
Flowable.just(7).<Publisher<Integer>>to(RxJava2Adapter::flowableToFlux)));
Flowable.just(1).compose(Flux::from),
Flowable.just(2).to(Flux::from),
Flowable.just(3).as(Flux::from),
Flowable.just(4).compose(RxJava2Adapter::flowableToFlux),
Flowable.just(5).<Publisher<Integer>>to(RxJava2Adapter::flowableToFlux)));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
@@ -39,8 +37,7 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Flowable.fromPublisher(Flux.just("foo")),
Flux.just("bar").transform(Flowable::fromPublisher),
Flux.just("baz").as(Flowable::fromPublisher),
RxJava2Adapter.fluxToFlowable(Flux.just("qux")),
Flux.just("quux").transform(RxJava2Adapter::fluxToFlowable));
Flux.just("qux").transform(RxJava2Adapter::fluxToFlowable));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -68,8 +65,7 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Flowable.fromPublisher(Mono.just(1)),
Mono.just(2).transform(Flowable::fromPublisher),
Mono.just(3).as(Flowable::fromPublisher),
RxJava2Adapter.monoToFlowable(Mono.just(4)),
Mono.just(5).transform(RxJava2Adapter::monoToFlowable));
Mono.just(4).transform(RxJava2Adapter::monoToFlowable));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -29,9 +29,7 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Flowable.just(2).as(RxJava2Adapter::flowableToFlux),
Flowable.just(3).as(RxJava2Adapter::flowableToFlux),
Flowable.just(4).as(RxJava2Adapter::flowableToFlux),
Flowable.just(5).as(RxJava2Adapter::flowableToFlux),
Flowable.just(6).as(RxJava2Adapter::flowableToFlux),
Flowable.just(7).as(RxJava2Adapter::flowableToFlux)));
Flowable.just(5).as(RxJava2Adapter::flowableToFlux)));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
@@ -39,8 +37,7 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Flux.just("foo").as(RxJava2Adapter::fluxToFlowable),
Flux.just("bar").as(RxJava2Adapter::fluxToFlowable),
Flux.just("baz").as(RxJava2Adapter::fluxToFlowable),
Flux.just("qux").as(RxJava2Adapter::fluxToFlowable),
Flux.just("quux").as(RxJava2Adapter::fluxToFlowable));
Flux.just("qux").as(RxJava2Adapter::fluxToFlowable));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -68,8 +65,7 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Mono.just(1).as(RxJava2Adapter::monoToFlowable),
Mono.just(2).as(RxJava2Adapter::monoToFlowable),
Mono.just(3).as(RxJava2Adapter::monoToFlowable),
Mono.just(4).as(RxJava2Adapter::monoToFlowable),
Mono.just(5).as(RxJava2Adapter::monoToFlowable));
Mono.just(4).as(RxJava2Adapter::monoToFlowable));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -34,6 +34,10 @@ final class TimeTemplatesTest implements RefasterTemplateTestCase {
ZoneId.from(ZoneOffset.UTC));
}
OffsetDateTime testInstantAtOffset() {
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
}
Clock testUtcClock() {
return Clock.system(ZoneOffset.UTC);
}

View File

@@ -34,6 +34,10 @@ final class TimeTemplatesTest implements RefasterTemplateTestCase {
ZoneOffset.UTC);
}
OffsetDateTime testInstantAtOffset() {
return Instant.EPOCH.atOffset(ZoneOffset.UTC);
}
Clock testUtcClock() {
return Clock.systemUTC();
}

View File

@@ -0,0 +1,23 @@
package tech.picnic.errorprone.refaster.util;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
/**
* A matcher for `identity` operations, for use with Refaster's {@code {Not,}Matches} annotation.
*/
public final class IsIdentity implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IDENTITY =
staticMethod()
.onClass((type, state) -> type.toString().contains("Function"))
.named("identity");
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
return IDENTITY.matches(tree, state);
}
}

View File

@@ -0,0 +1,55 @@
package tech.picnic.errorprone.refaster.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodInvocationTree;
import org.junit.jupiter.api.Test;
final class IsIdentityTest {
@Test
void matches() {
CompilationTestHelper.newInstance(TestChecker.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static java.util.function.Function.identity;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.Streams;",
"",
"class A {",
" void positive() {",
" // BUG: Diagnostic contains:",
" java.util.function.Function.identity();",
" // BUG: Diagnostic contains:",
" com.google.common.base.Functions.identity();",
" }",
"",
" public static void identity() { }",
"",
" static void negative() {",
" identity();",
" A.identity();",
" }",
"}")
.doTest();
}
/** A {@link BugChecker} which simply delegates to {@link IsIdentity}. */
@BugPattern(name = "TestChecker", summary = "Flags identity method invocations", severity = ERROR)
public static final class TestChecker extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return new IsIdentity().matches(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
}
}