mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
3 Commits
sschroever
...
sschroever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3b5ead02b | ||
|
|
7daa39a0b5 | ||
|
|
5fc7bc29ee |
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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('/', '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ final class RefasterRulesTest {
|
||||
AssortedRules.class,
|
||||
BigDecimalRules.class,
|
||||
BugCheckerRules.class,
|
||||
CharSequenceRules.class,
|
||||
ClassRules.class,
|
||||
CollectionRules.class,
|
||||
ComparatorRules.class,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
10
pom.xml
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user