Compare commits

...

3 Commits

Author SHA1 Message Date
Stephan Schroevers
c3b5ead02b WIP: Model modernizer-maven-plugin 2024-12-29 00:22:32 +01:00
Rick Ossendrijver
7daa39a0b5 Update comments referencing Refaster rule limitation (#46)
Issue google/error-prone#2706 has been resolved; we have yet to decide
how to proceed.
2024-12-27 05:30:30 +01:00
Rick Ossendrijver
5fc7bc29ee Introduce CharSequenceRules Refaster rule collection (#1480)
Resolves #1394.
2024-12-26 17:47:57 +01:00
19 changed files with 698 additions and 26 deletions

View File

@@ -4,6 +4,7 @@
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED

View File

@@ -72,6 +72,10 @@
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
@@ -162,6 +166,12 @@
<artifactId>assertj-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<scope>runtime</scope>
<!-- XXX: Consider making optional? Ship only the definitions? -->
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>

View File

@@ -0,0 +1,371 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableTable.toImmutableTable;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.REFACTORING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.predicates.TypePredicates.isDescendantOf;
import static com.google.errorprone.predicates.TypePredicates.isExactType;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.auto.value.AutoValue.CopyAnnotations;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableTable;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.annotations.Var;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.IdentifierTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.jvm.Target;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} that flags the same legacy APIs as the <a
* href="https://github.com/gaul/modernizer-maven-plugin">Modernizer Maven Plugin</a>.
*
* <p>This checker is primarily useful for people who run Error Prone anyway; it obviates the need
* for an additional source code analysis pass using another Maven plugin.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid constants and methods superseded by more recent equivalents",
link = BUG_PATTERNS_BASE_URL + "Modernizer",
linkType = CUSTOM,
severity = SUGGESTION,
tags = REFACTORING)
public final class Modernizer extends BugChecker
implements AnnotationTreeMatcher,
IdentifierTreeMatcher,
MemberReferenceTreeMatcher,
MemberSelectTreeMatcher,
NewClassTreeMatcher {
private static final long serialVersionUID = 1L;
// XXX: Load lazily?
private final ImmutableTable<String, Matcher<ExpressionTree>, String> violations =
loadViolations();
/** Instantiates a new {@link MockitoStubbing} instance. */
public Modernizer() {}
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
// XXX: Use or drop
// XXX: See
// https://github.com/google/guava-beta-checker/commit/9b26aa980be7f70631921fd6695013547728eb1e;
// we may be on the right track without this.
return Description.NO_MATCH;
}
@Override
public Description matchIdentifier(IdentifierTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
return match(tree.getIdentifier(), tree, state);
}
@Override
public Description matchNewClass(NewClassTree tree, VisitorState state) {
Symbol createdType =
requireNonNull(ASTHelpers.getSymbol(tree).getEnclosingElement(), "No enclosing class");
return match(createdType.getQualifiedName(), tree, state);
}
private Description match(Name identifier, ExpressionTree tree, VisitorState state) {
return violations.row(identifier.toString()).entrySet().stream()
.filter(e -> e.getKey().matches(tree, state))
.findFirst()
.map(e -> buildDescription(tree).setMessage(e.getValue()).build())
.orElse(Description.NO_MATCH);
}
private ImmutableTable<String, Matcher<ExpressionTree>, String> loadViolations() {
InputStream resource = getClass().getResourceAsStream("/modernizer.xml");
// XXX: Or silently skip?
checkState(resource != null, "Modernizer configuration not found on classpath");
XmlMapper mapper = new XmlMapper();
try (resource) {
return mapper.readValue(resource, Violations.class).getViolation().stream()
.filter(v -> v.getChecker().isPresent())
.collect(
toImmutableTable(
Violation::getIdentifier,
v -> v.getChecker().orElseThrow(),
Violation::getComment));
} catch (IOException e) {
throw new UncheckedIOException("Failed to parse Modernizer configuration", e);
}
}
// XXX: Further simplify with Auto Value?
@Immutable
static final class Violations {
@JacksonXmlElementWrapper(useWrapping = false)
private final ImmutableList<Violation> violation;
@JsonCreator
private Violations(@JsonProperty("violation") List<Violation> violation) {
this.violation = ImmutableList.copyOf(violation);
}
// XXX: Jackson relies on this naming and visibility. Ugh.
public ImmutableList<Violation> getViolation() {
return violation;
}
}
@Immutable
@AutoValue
abstract static class Violation {
private static final Pattern NAME_PATTERN =
Pattern.compile(
"(?<type>[^.]+)(?:\\.(?<member>[^:]+):(?:\\((?<params>[^)]*)\\))?(?<return>[^()]+))?");
abstract Optional<Target> getTarget();
abstract String getIdentifier();
@CopyAnnotations
@SuppressWarnings("Immutable")
abstract Matcher<ExpressionTree> getMatcher();
abstract String getComment();
Optional<Matcher<ExpressionTree>> getChecker() {
return getTarget().map(t -> allOf(getMatcher(), targetMatcher(t)));
}
// XXX: Overkill? Not if we use auto value.
// XXX: Modernizer also flags annotation declarations, presumably by type.
// XXX: `ExpressionTree` is wrong here. Depends on type.
@JsonCreator
static Violation create(
@JsonProperty("version") String version,
@JsonProperty("name") String signature,
@JsonProperty("comment") String comment) {
Optional<Target> target = Optional.ofNullable(Target.lookup(version));
java.util.regex.Matcher matcher = NAME_PATTERN.matcher(signature);
checkState(matcher.matches(), "Failed to parse signature '%s'", signature);
String type =
replaceSlashes(requireNonNull(matcher.group("type"), "Signature must contain type"));
String member = matcher.group("member");
if (member == null) {
// XXX: Should not implement this interface. Something like:
// violations.put(type, allOf(isSubtypeOf(type), versionRequirement), this.comment)
return new AutoValue_Modernizer_Violation(target, type, (t, s) -> false, comment);
}
String params = matcher.group("params");
if (params == null) {
return new AutoValue_Modernizer_Violation(target, member, isField(type), comment);
}
ImmutableList<Supplier<Type>> parameters = parseParams(params);
if ("\"<init>\"".equals(member)) {
return new AutoValue_Modernizer_Violation(
target, type, isConstructor(type, parameters), comment);
}
// XXX: Should we disallow _extension_ of this method?
return new AutoValue_Modernizer_Violation(
target, member, isMethod(type, parameters), comment);
}
private static Matcher<ExpressionTree> targetMatcher(Target target) {
return (tree, state) -> target.compareTo(getTargetVersion(state)) <= 0;
}
private static Target getTargetVersion(VisitorState state) {
return Target.instance(
Optional.ofNullable(state.context.get(JavacTask.class))
.filter(BasicJavacTask.class::isInstance)
.map(BasicJavacTask.class::cast)
.map(BasicJavacTask::getContext)
.orElse(state.context));
}
private static Matcher<ExpressionTree> isField(String onDescendantOf) {
return isMember(ElementKind::isField, isDescendantOf(onDescendantOf), ImmutableList.of());
}
private static Matcher<ExpressionTree> isConstructor(
String ofClass, ImmutableList<Supplier<Type>> withParameters) {
return isMember(k -> k == ElementKind.CONSTRUCTOR, isExactType(ofClass), withParameters);
}
private static Matcher<ExpressionTree> isMethod(
String onDescendantOf, ImmutableList<Supplier<Type>> withParameters) {
return isMember(k -> k == ElementKind.METHOD, isDescendantOf(onDescendantOf), withParameters);
}
private static Matcher<ExpressionTree> isMember(
Predicate<ElementKind> ofKind,
TypePredicate ownedBy,
ImmutableList<Supplier<Type>> withParameters) {
return (tree, state) ->
Optional.ofNullable(ASTHelpers.getSymbol(tree))
.filter(s -> ofKind.test(s.getKind()))
.filter(s -> isOwnedBy(s, ownedBy, state))
.filter(s -> hasSameParameters(s, withParameters, state))
.isPresent();
}
private static boolean isOwnedBy(Symbol symbol, TypePredicate expected, VisitorState state) {
Symbol owner = symbol.getEnclosingElement();
return owner != null && expected.apply(owner.asType(), state);
}
private static boolean hasSameParameters(
Symbol method, ImmutableList<Supplier<Type>> expected, VisitorState state) {
List<Type> actual = method.asType().getParameterTypes();
if (actual.size() != expected.size()) {
return false;
}
for (int i = 0; i < actual.size(); ++i) {
if (!ASTHelpers.isSameType(actual.get(i), expected.get(i).get(state), state)) {
return false;
}
}
return true;
}
private static ImmutableList<Supplier<Type>> parseParams(String params) {
ImmutableList.Builder<Supplier<Type>> types = ImmutableList.builder();
@Var int index = 0;
while (index < params.length()) {
index = parseType(params, index, types::add);
}
return types.build();
}
private static int parseType(String params, int index, Consumer<Supplier<Type>> sink) {
return switch (params.charAt(index)) {
case '[' -> parseArrayType(params, index, sink);
case 'L' -> parseTypeReference(params, index, sink);
default -> parsePrimitiveType(params, index, sink);
};
}
private static int parseArrayType(String params, int index, Consumer<Supplier<Type>> sink) {
int typeIndex = index + 1;
checkArgument(
params.length() > typeIndex && params.charAt(index) == '[',
"Cannot parse array type in parameter string '%s' at index %s",
params,
index);
return parseType(
params,
typeIndex,
type ->
sink.accept(s -> s.getType(type.get(s), /* isArray= */ true, ImmutableList.of())));
}
private static int parsePrimitiveType(String params, int index, Consumer<Supplier<Type>> sink) {
String primitive =
Optional.of(params)
.filter(p -> p.length() > index)
.flatMap(p -> fromPrimitiveAlias(p.charAt(index)))
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Cannot parse primitive type in parameter string '%s' at index %s",
params, index)));
sink.accept(s -> s.getTypeFromString(primitive));
return index + 1;
}
private static Optional<String> fromPrimitiveAlias(char alias) {
return switch (alias) {
case 'Z' -> Optional.of("boolean");
case 'B' -> Optional.of("byte");
case 'C' -> Optional.of("char");
case 'S' -> Optional.of("short");
case 'I' -> Optional.of("int");
case 'J' -> Optional.of("long");
case 'F' -> Optional.of("float");
case 'D' -> Optional.of("double");
default -> Optional.empty();
};
}
private static int parseTypeReference(String params, int index, Consumer<Supplier<Type>> sink) {
int identifierIndex = index + 1;
if (params.length() > identifierIndex && params.charAt(index) == 'L') {
int delimiter = params.indexOf(';', identifierIndex);
if (delimiter > index) {
sink.accept(
s ->
s.getTypeFromString(
replaceSlashes(params.substring(identifierIndex, delimiter))));
return delimiter + 1;
}
}
throw new IllegalArgumentException(
String.format(
"Cannot parse reference type in parameter string '%s' at index %s", params, index));
}
private static String replaceSlashes(String typeName) {
return typeName.replace('/', '.');
}
}
}

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link CharSequence}s. */
@OnlineDocumentation
final class CharSequenceRules {
private CharSequenceRules() {}
/**
* Prefer {@link CharSequence#isEmpty()} over alternatives that consult the char sequence's
* length.
*/
// XXX: Drop this rule once we (and OpenRewrite) no longer support projects targeting Java 14 or
// below.
static final class CharSequenceIsEmpty {
@BeforeTemplate
boolean before(CharSequence charSequence) {
return Refaster.anyOf(
charSequence.length() == 0, charSequence.length() <= 0, charSequence.length() < 1);
}
@AfterTemplate
@AlsoNegation
boolean after(CharSequence charSequence) {
return charSequence.isEmpty();
}
}
}

View File

@@ -36,8 +36,7 @@ final class ImmutableListMultimapRules {
* Prefer {@link ImmutableListMultimap#builder()} over the associated constructor on constructions
* that produce a less-specific type.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableListMultimapBuilder<K, V> {
@BeforeTemplate
ImmutableMultimap.Builder<K, V> before() {

View File

@@ -28,8 +28,7 @@ final class ImmutableListRules {
private ImmutableListRules() {}
/** Prefer {@link ImmutableList#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableListBuilder<T> {
@BeforeTemplate
ImmutableList.Builder<T> before() {

View File

@@ -31,8 +31,7 @@ final class ImmutableMapRules {
private ImmutableMapRules() {}
/** Prefer {@link ImmutableMap#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableMapBuilder<K, V> {
@BeforeTemplate
ImmutableMap.Builder<K, V> before() {

View File

@@ -21,8 +21,7 @@ final class ImmutableMultisetRules {
private ImmutableMultisetRules() {}
/** Prefer {@link ImmutableMultiset#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableMultisetBuilder<T> {
@BeforeTemplate
ImmutableMultiset.Builder<T> before() {

View File

@@ -29,8 +29,7 @@ final class ImmutableSetMultimapRules {
private ImmutableSetMultimapRules() {}
/** Prefer {@link ImmutableSetMultimap#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableSetMultimapBuilder<K, V> {
@BeforeTemplate
ImmutableSetMultimap.Builder<K, V> before() {

View File

@@ -29,8 +29,7 @@ final class ImmutableSetRules {
private ImmutableSetRules() {}
/** Prefer {@link ImmutableSet#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableSetBuilder<T> {
@BeforeTemplate
ImmutableSet.Builder<T> before() {

View File

@@ -37,8 +37,7 @@ final class ImmutableSortedMapRules {
* Prefer {@link ImmutableSortedMap#naturalOrder()} over the alternative that requires explicitly
* providing the {@link Comparator}.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableSortedMapNaturalOrderBuilder<K extends Comparable<? super K>, V> {
@BeforeTemplate
ImmutableSortedMap.Builder<K, V> before() {
@@ -55,8 +54,7 @@ final class ImmutableSortedMapRules {
* Prefer {@link ImmutableSortedMap#reverseOrder()} over the alternative that requires explicitly
* providing the {@link Comparator}.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
// XXX: This rule may drop generic type information, leading to non-compilable code.
static final class ImmutableSortedMapReverseOrderBuilder<K extends Comparable<? super K>, V> {
@BeforeTemplate
ImmutableSortedMap.Builder<K, V> before() {

View File

@@ -29,11 +29,14 @@ final class StringRules {
private StringRules() {}
/** Prefer {@link String#isEmpty()} over alternatives that consult the string's length. */
// XXX: Now that we build with JDK 15+, this rule can be generalized to cover all `CharSequence`
// subtypes. This does require a mechanism (perhaps an annotation, or a separate Maven module) to
// make sure that non-String expressions are rewritten only if client code also targets JDK 15+.
// XXX: Drop this rule once we (and OpenRewrite) no longer support projects targeting Java 14 or
// below. The `CharSequenceIsEmpty` rule then suffices. (This rule exists so that e.g. projects
// that target JDK 11 can disable `CharSequenceIsEmpty` without losing a valuable rule.)
// XXX: Look into a more general approach to supporting different Java language levels, such as
// rule selection based on some annotation, or a separate Maven module.
static final class StringIsEmpty {
@BeforeTemplate
@SuppressWarnings("CharSequenceIsEmpty" /* This is a more specific template. */)
boolean before(String str) {
return Refaster.anyOf(str.length() == 0, str.length() <= 0, str.length() < 1);
}

View File

@@ -0,0 +1,221 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
public final class ModernizerTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(Modernizer.class, getClass());
// XXX: Also add calls that should not be flagged.
// XXX: Test extension, field references, instance methods, static methods.
// methods with primitives, primitive arrays, references, reference arrays
// zero, one two args.
// XXX: Also test constructors!
// XXX: Test that the appropriate "prefer" message is emitted.
// XXX: List the test cases in `ModernizerTest`?
@Test
void fieldIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Charsets.ISO_8859_1;",
"",
"import com.google.common.base.Charsets;",
"import java.nio.charset.StandardCharsets;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o1 = ISO_8859_1;",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o2 = Charsets.ISO_8859_1;",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o3 = com.google.common.base.Charsets.ISO_8859_1;",
"",
" Object o4 = StandardCharsets.ISO_8859_1;",
" Object o5 = java.nio.charset.StandardCharsets.ISO_8859_1;",
" }",
"}")
.doTest();
}
@Test
void nullaryMethodIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Optional.absent;",
"",
"import com.google.common.base.Optional;",
"import java.util.function.Supplier;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Optional.absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" com.google.common.base.Optional.absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Supplier<?> s1 = Optional::absent;",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Supplier<?> s2 = com.google.common.base.Optional::absent;",
"",
" java.util.Optional.empty();",
" Supplier<?> s3 = java.util.Optional::empty;",
"",
" Dummy.absent();",
" }",
"",
" static final class Dummy {",
" static Optional<?> absent() {",
" return null;",
" }",
" }",
"}")
.doTest();
}
@Test
void unaryMethodWithIntegerArgumentIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.collect.Lists.newArrayListWithCapacity;",
"",
"import com.google.common.collect.Lists;",
"import java.util.ArrayList;",
"import java.util.function.IntFunction;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" newArrayListWithCapacity(0);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" Lists.newArrayListWithCapacity(1);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" com.google.common.collect.Lists.newArrayListWithCapacity(2);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" IntFunction<?> f1 = Lists::newArrayListWithCapacity;",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" IntFunction<?> f2 = com.google.common.collect.Lists::newArrayListWithCapacity;",
"",
" new ArrayList<>(3);",
" IntFunction<?> f3 = ArrayList::new;",
" IntFunction<?> f4 = java.util.ArrayList::new;",
"",
" Dummy.newArrayListWithCapacity(4);",
" }",
"",
" static final class Dummy {",
" static ArrayList<?> newArrayListWithCapacity(int initialArraySize) {",
" return null;",
" }",
" }",
"}")
.doTest();
}
@Test
void binaryMethodWithObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Objects.equal;",
"",
"import com.google.common.base.Objects;",
"import java.util.function.BiPredicate;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" Objects.equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" com.google.common.base.Objects.equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" BiPredicate<?, ?> p1 = Objects::equal;",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" BiPredicate<?, ?> p2 = com.google.common.base.Objects::equal;",
"",
" java.util.Objects.equals(null, null);",
" BiPredicate<?, ?> p3 = java.util.Objects::equals;",
"",
" Dummy.equal(null, null);",
" }",
"",
" static final class Dummy {",
" static boolean equal(Object a, Object b) {",
" return false;",
" }",
" }",
"}")
.doTest();
}
@Test
void varargsMethodWithObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.base.Objects;",
"import java.util.function.ToIntFunction;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" Objects.hashCode((Object) null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" com.google.common.base.Objects.hashCode(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" ToIntFunction<?> f1 = Objects::hashCode;",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" ToIntFunction<?> f2 = com.google.common.base.Objects::hashCode;",
"",
" java.util.Objects.hash(null, null, null);",
" ToIntFunction<?> f3 = java.util.Objects::hash;",
"",
" Dummy.hashCode(null, null, null, null);",
" }",
"",
" static final class Dummy {",
" static int hashCode(Object... objects) {",
" return 0;",
" }",
" }",
"}")
.doTest();
}
@Test
void binaryConstructorWithByteArrayAndObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.io.UnsupportedEncodingException;",
"import java.nio.charset.StandardCharsets;",
"",
"class A {",
" void m() throws UnsupportedEncodingException {",
" // BUG: Diagnostic contains: Prefer java.lang.String.<init>(byte[], java.nio.charset.Charset)",
" new String(new byte[0], \"\");",
" // BUG: Diagnostic contains: Prefer java.lang.String.<init>(byte[], java.nio.charset.Charset)",
" new java.lang.String(new byte[] {}, toString());",
"",
" new String(new byte[0], StandardCharsets.UTF_8);",
" new java.lang.String(new byte[0], StandardCharsets.UTF_8);",
"",
" new Dummy(new byte[0], \"\");",
" }",
"",
" static final class Dummy {",
" Dummy(byte bytes[], String charsetName) {}",
" }",
"}")
.doTest();
}
}

View File

@@ -36,6 +36,7 @@ final class RefasterRulesTest {
AssortedRules.class,
BigDecimalRules.class,
BugCheckerRules.class,
CharSequenceRules.class,
ClassRules.class,
CollectionRules.class,
ComparatorRules.class,

View File

@@ -0,0 +1,16 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class CharSequenceRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Boolean> testCharSequenceIsEmpty() {
return ImmutableSet.of(
new StringBuilder("foo").length() == 0,
new StringBuilder("bar").length() <= 0,
new StringBuilder("baz").length() < 1,
new StringBuilder("qux").length() != 0,
new StringBuilder("quux").length() > 0,
new StringBuilder("corge").length() >= 1);
}
}

View File

@@ -0,0 +1,16 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class CharSequenceRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Boolean> testCharSequenceIsEmpty() {
return ImmutableSet.of(
new StringBuilder("foo").isEmpty(),
new StringBuilder("bar").isEmpty(),
new StringBuilder("baz").isEmpty(),
!new StringBuilder("qux").isEmpty(),
!new StringBuilder("quux").isEmpty(),
!new StringBuilder("corge").isEmpty());
}
}

View File

@@ -28,9 +28,9 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
"foo".length() == 0,
"bar".length() <= 0,
"baz".length() < 1,
"foo".length() != 0,
"bar".length() > 0,
"baz".length() >= 1);
"qux".length() != 0,
"quux".length() > 0,
"corge".length() >= 1);
}
boolean testStringIsEmptyPredicate() {

View File

@@ -31,9 +31,9 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
"foo".isEmpty(),
"bar".isEmpty(),
"baz".isEmpty(),
!"foo".isEmpty(),
!"bar".isEmpty(),
!"baz".isEmpty());
!"qux".isEmpty(),
!"quux".isEmpty(),
!"corge".isEmpty());
}
boolean testStringIsEmptyPredicate() {

10
pom.xml
View File

@@ -93,6 +93,7 @@
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
@@ -113,7 +114,7 @@
-Xmx${argLine.xmx}
<!-- Configure the Byte Buddy Java agent used by Mockito to create
mocks. -->
-javaagent:${org.mockito:mockito-core:jar}
<!-- -javaagent:${org.mockito:mockito-core:jar} -->
<!-- This argument cannot be set through Surefire's
'systemPropertyVariables' configuration setting. Setting the file
encoding is necessary because forked unit test invocations
@@ -454,6 +455,11 @@
<artifactId>checker-qual</artifactId>
<version>3.48.3</version>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
@@ -979,6 +985,7 @@
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
@@ -1355,6 +1362,7 @@
<!-- XXX: Get projects referencing just "BSD"
to explicitly state the clause count. -->
BSD-2-Clause
| The BSD 2-Clause License
| The BSD License
</licenseMerge>
<licenseMerge>