mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 15:49:33 +00:00
Compare commits
2 Commits
sschroever
...
sschroever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a698c7e6a0 | ||
|
|
b8e22ffef0 |
@@ -3,7 +3,6 @@
|
||||
-XX:+UseParallelGC
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.api=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
|
||||
|
||||
@@ -52,10 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-xml</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
@@ -67,11 +64,6 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
@@ -151,12 +143,6 @@
|
||||
<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>
|
||||
|
||||
@@ -1,383 +0,0 @@
|
||||
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.NONE;
|
||||
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 java.util.Objects.requireNonNull;
|
||||
|
||||
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.predicates.TypePredicates;
|
||||
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} which 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 superceded by more recent equivalents",
|
||||
linkType = NONE,
|
||||
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();
|
||||
|
||||
@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) {
|
||||
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
|
||||
.map(Symbol::getEnclosingElement)
|
||||
.map(Symbol::getQualifiedName)
|
||||
.map(name -> match(name, tree, state))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private Description match(Name identifier, ExpressionTree tree, VisitorState state) {
|
||||
return this.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 =
|
||||
replaceSlahes(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, TypePredicates.isDescendantOf(onDescendantOf), ImmutableList.of());
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> isConstructor(
|
||||
String ofClass, ImmutableList<Supplier<Type>> withParameters) {
|
||||
return isMember(
|
||||
k -> k == ElementKind.CONSTRUCTOR, TypePredicates.isExactType(ofClass), withParameters);
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> isMethod(
|
||||
String onDescendantOf, ImmutableList<Supplier<Type>> withParameters) {
|
||||
return isMember(
|
||||
k -> k == ElementKind.METHOD,
|
||||
TypePredicates.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) {
|
||||
switch (params.charAt(index)) {
|
||||
case '[':
|
||||
return parseArrayType(params, index, sink);
|
||||
case 'L':
|
||||
return parseTypeReference(params, index, sink);
|
||||
default:
|
||||
return 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) {
|
||||
switch (alias) {
|
||||
case 'Z':
|
||||
return Optional.of("boolean");
|
||||
case 'B':
|
||||
return Optional.of("byte");
|
||||
case 'C':
|
||||
return Optional.of("char");
|
||||
case 'S':
|
||||
return Optional.of("short");
|
||||
case 'I':
|
||||
return Optional.of("int");
|
||||
case 'J':
|
||||
return Optional.of("long");
|
||||
case 'F':
|
||||
return Optional.of("float");
|
||||
case 'D':
|
||||
return Optional.of("double");
|
||||
default:
|
||||
return 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(replaceSlahes(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 replaceSlahes(String typeName) {
|
||||
return typeName.replace('/', '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
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.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.function.BiFunction;
|
||||
import reactor.core.publisher.Mono;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@link Mono} operations that are known to be vacuous, given that
|
||||
* they are invoked on a {@link Mono} that is known not to complete empty.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid vacuous operations on known non-empty `Mono`s",
|
||||
linkType = NONE,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
// XXX: This check does not simplify `someFlux.defaultIfEmpty(T).{defaultIfEmpty(T),hasElements()}`,
|
||||
// as `someFlux.defaultIfEmpty(T)` yields a `Flux` rather than a `Mono`. Consider adding support for
|
||||
// these cases.
|
||||
// XXX: Given more advanced analysis many more expressions could be flagged. Consider
|
||||
// `Mono.just(someValue)`, `Flux.just(someNonEmptySequence)`,
|
||||
// `someMono.switchIfEmpty(someProvablyNonEmptyMono)` and many other variants.
|
||||
// XXX: Consider implementing a similar check for `Publisher`s that are known to complete without
|
||||
// emitting a value (e.g. `Mono.empty()`, `someFlux.then()`, ...), or known not to complete normally
|
||||
// (`Mono.never()`, `someFlux.repeat()`, `Mono.error(...)`, ...). The latter category could
|
||||
// potentially be split out further.
|
||||
public final class NonEmptyMono extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "single", "switchIfEmpty");
|
||||
private static final Matcher<ExpressionTree> NON_EMPTY_MONO =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.namedAnyOf(
|
||||
"all",
|
||||
"any",
|
||||
"collect",
|
||||
"collectList",
|
||||
"collectMap",
|
||||
"collectMultimap",
|
||||
"collectSortedList",
|
||||
"count",
|
||||
"elementAt",
|
||||
"hasElement",
|
||||
"hasElements",
|
||||
"last",
|
||||
"reduceWith",
|
||||
"single"),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.named("reduce")
|
||||
.withParameters(Object.class.getName(), BiFunction.class.getName()),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!MONO_SIZE_CHECK.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (!NON_EMPTY_MONO.matches(receiver, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree, SuggestedFix.replace(tree, SourceCode.treeToString(receiver, state)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Flowable;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import org.assertj.core.util.Streams;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.adapter.rxjava.RxJava2Adapter;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/** Refaster templates which replace RxJava expressions with equivalent Reactor assertions. */
|
||||
// XXX: Document the approach and any limitations; see the `TestNGToAssertJTemplates` documentation.
|
||||
// XXX: Document that the templates use `Completable` rather than `CompletableSource`, etc.
|
||||
// XXX: Have separate files for Completable, Maybe, Single, Flowable?
|
||||
final class RxJavaToReactorTemplates {
|
||||
private RxJavaToReactorTemplates() {}
|
||||
|
||||
// XXX: Handle array and varargs cases.
|
||||
// XXX: Simplify rule with `Completable.amb(Arrays.asList(sources))`?
|
||||
static final class CompletableAmbArray {
|
||||
@BeforeTemplate
|
||||
Completable before(Completable... sources) {
|
||||
return Completable.ambArray(sources);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Completable... sources) {
|
||||
return Mono.firstWithSignal(
|
||||
Arrays.stream(sources)
|
||||
.map(RxJava2Adapter::completableToMono)
|
||||
.collect(toImmutableList()))
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CompletableAmb {
|
||||
@BeforeTemplate
|
||||
Completable before(Iterable<? extends Completable> sources) {
|
||||
return Completable.amb(sources);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Iterable<? extends Completable> sources) {
|
||||
return Mono.firstWithSignal(
|
||||
Streams.stream(sources)
|
||||
.map(RxJava2Adapter::completableToMono)
|
||||
.collect(toImmutableList()))
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CompletableComplete {
|
||||
@BeforeTemplate
|
||||
Completable before() {
|
||||
return Completable.complete();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after() {
|
||||
return Mono.empty().as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Handle array and varargs cases.
|
||||
// XXX: Simplify rule with `Completable.amb(Arrays.asList(sources))`?
|
||||
static final class CompletableConcatArray {
|
||||
@BeforeTemplate
|
||||
Completable before(Completable... sources) {
|
||||
return Completable.concatArray(sources);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Completable... sources) {
|
||||
return Flux.concat(
|
||||
Arrays.stream(sources)
|
||||
.map(RxJava2Adapter::completableToMono)
|
||||
.collect(toImmutableList()))
|
||||
.then()
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Simplify rule with `Completable.concat(Flux.fromIterable(sources))`?
|
||||
static final class CompletableConcatIterable {
|
||||
@BeforeTemplate
|
||||
Completable before(Iterable<? extends Completable> sources) {
|
||||
return Completable.concat(sources);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Iterable<? extends Completable> sources) {
|
||||
return Flux.concat(Flux.fromIterable(sources).map(RxJava2Adapter::completableToMono))
|
||||
.then()
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Simplify rule with `Completable.concat(sources, 2)`?
|
||||
// XXX: Arguably we should, since the Reactor prefetch is `Queues.XS_BUFFER_SIZE`.
|
||||
static final class CompletableConcatPublisher {
|
||||
@BeforeTemplate
|
||||
Completable before(Publisher<? extends Completable> sources) {
|
||||
return Completable.concat(sources);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Publisher<? extends Completable> sources) {
|
||||
return Flux.concat(Flux.from(sources).map(RxJava2Adapter::completableToMono))
|
||||
.then()
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CompletableConcatPublisherPrefetch {
|
||||
@BeforeTemplate
|
||||
Completable before(Publisher<? extends Completable> sources, int prefetch) {
|
||||
return Completable.concat(sources, prefetch);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Publisher<? extends Completable> sources, int prefetch) {
|
||||
return Flux.concat(Flux.from(sources).map(RxJava2Adapter::completableToMono), prefetch)
|
||||
.then()
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Migrate `Completable#create(CompletableOnSubscribe)`
|
||||
|
||||
// XXX: Migrate `Completable#unsafeCreate(CompletableSource)`
|
||||
|
||||
static final class CompletableDefer {
|
||||
@BeforeTemplate
|
||||
Completable before(Callable<? extends Completable> completableSupplier) {
|
||||
return Completable.defer(completableSupplier);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Callable<? extends Completable> completableSupplier) {
|
||||
return Mono.defer(
|
||||
() ->
|
||||
RxJava2ReactorMigrationUtil.getUnchecked(completableSupplier)
|
||||
.as(RxJava2Adapter::completableToMono))
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CompletableErrorDeferred {
|
||||
@BeforeTemplate
|
||||
Completable before(Callable<? extends Throwable> errorSupplier) {
|
||||
return Completable.error(errorSupplier);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Callable<? extends Throwable> errorSupplier) {
|
||||
return Mono.error(RxJava2ReactorMigrationUtil.callableAsSupplier(errorSupplier))
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CompletableError {
|
||||
@BeforeTemplate
|
||||
Completable before(Throwable error) {
|
||||
return Completable.error(error);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Throwable error) {
|
||||
return Mono.error(error).as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Migrate `Completable#fromAction(Action)`
|
||||
|
||||
static final class CompletableFromCallable<T> {
|
||||
@BeforeTemplate
|
||||
Completable before(Callable<T> callable) {
|
||||
return Completable.fromCallable(callable);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(Callable<T> callable) {
|
||||
return Mono.fromSupplier(RxJava2ReactorMigrationUtil.callableAsSupplier(callable))
|
||||
.as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Also handle `Future`s that don't extend `CompletableFuture`.
|
||||
static final class CompletableFromFuture<T> {
|
||||
@BeforeTemplate
|
||||
Completable before(CompletableFuture<T> future) {
|
||||
return Completable.fromFuture(future);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Completable after(CompletableFuture<T> future) {
|
||||
return Mono.fromFuture(future).as(RxJava2Adapter::monoToCompletable);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Next up: migrate `Completable#fromMaybe(Maybe)`
|
||||
|
||||
// XXX: Move to a separate Maven module.
|
||||
static final class RxJava2ReactorMigrationUtil {
|
||||
private RxJava2ReactorMigrationUtil() {}
|
||||
|
||||
// XXX: Rename.
|
||||
// XXX: Introduce Refaster rules to drop this wrapper when possible.
|
||||
static <T> T getUnchecked(Callable<T> callable) {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Callable threw checked exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Rename.
|
||||
// XXX: Introduce Refaster rules to drop this wrapper when possible.
|
||||
static <T> Supplier<T> callableAsSupplier(Callable<T> callable) {
|
||||
return () -> {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Callable threw checked exception", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T, U, R> BiFunction<? super T, ? super U, ? extends R> toJdkBiFunction(
|
||||
io.reactivex.functions.BiFunction<? super T, ? super U, ? extends R> biFunction) {
|
||||
return (t, u) -> {
|
||||
try {
|
||||
return biFunction.apply(t, u);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("BiFunction threw checked exception", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////// FLOWABLE - MOVE!
|
||||
|
||||
static final class FlowableCombineLatest<T1, T2, R> {
|
||||
@BeforeTemplate
|
||||
Flowable<R> before(
|
||||
Publisher<? extends T1> publisher1,
|
||||
Publisher<? extends T2> publisher2,
|
||||
io.reactivex.functions.BiFunction<? super T1, ? super T2, ? extends R> combiner) {
|
||||
return Flowable.combineLatest(publisher1, publisher2, combiner);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flowable<R> after(
|
||||
Publisher<? extends T1> publisher1,
|
||||
Publisher<? extends T2> publisher2,
|
||||
io.reactivex.functions.BiFunction<? super T1, ? super T2, ? extends R> combiner) {
|
||||
// XXX: Generic type parameters are specified to appease IDEA; review.
|
||||
return RxJava2Adapter.fluxToFlowable(
|
||||
Flux.<T1, T2, R>combineLatest(
|
||||
publisher1, publisher2, RxJava2ReactorMigrationUtil.toJdkBiFunction(combiner)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class NonEmptyMonoTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static com.google.common.collect.ImmutableList.toImmutableList;",
|
||||
"import static java.util.function.Function.identity;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.common.collect.ImmutableMap;",
|
||||
"import java.util.ArrayList;",
|
||||
"import java.util.HashMap;",
|
||||
"import java.util.List;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"import reactor.core.publisher.Mono;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Mono.just(1).defaultIfEmpty(2);",
|
||||
" Mono.just(1).single();",
|
||||
" Mono.just(1).switchIfEmpty(Mono.just(2));",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).all(x -> true).defaultIfEmpty(true);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).any(x -> true).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collect(toImmutableList()).switchIfEmpty(Mono.just(ImmutableList.of()));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collect(ArrayList::new, List::add).defaultIfEmpty(new ArrayList<>());",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectList().single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMap(identity()).switchIfEmpty(Mono.just(ImmutableMap.of()));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMap(identity(), identity()).defaultIfEmpty(ImmutableMap.of());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMap(identity(), identity(), HashMap::new).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMultimap(identity()).switchIfEmpty(Mono.just(ImmutableMap.of()));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMultimap(identity(), identity()).defaultIfEmpty(ImmutableMap.of());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectMultimap(identity(), identity(), HashMap::new).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectSortedList().defaultIfEmpty(ImmutableList.of());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).collectSortedList((o1, o2) -> 0).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).count().switchIfEmpty(Mono.just(2L));",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).elementAt(0).defaultIfEmpty(1);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).elementAt(0, 2).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).hasElement(2).switchIfEmpty(Mono.just(true));",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).hasElements().defaultIfEmpty(true);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).last().single();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).last(2).switchIfEmpty(Mono.just(3));",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).reduceWith(() -> 0, Integer::sum).defaultIfEmpty(2);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).single().single();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).single(2).switchIfEmpty(Mono.just(3));",
|
||||
"",
|
||||
" Flux.just(1).reduce(Integer::sum).defaultIfEmpty(2);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).reduce(2, Integer::sum).single();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Mono.just(1).defaultIfEmpty(1).switchIfEmpty(Mono.just(2));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Mono.just(1).hasElement().defaultIfEmpty(true);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Mono.just(1).single().single();",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static com.google.common.collect.ImmutableList.toImmutableList;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"import reactor.core.publisher.Mono;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Flux.just(1).collect(toImmutableList()).single();",
|
||||
" Flux.just(1).collect(toImmutableList()).defaultIfEmpty(ImmutableList.of());",
|
||||
" Flux.just(1).collect(toImmutableList()).switchIfEmpty(Mono.just(ImmutableList.of()));",
|
||||
"",
|
||||
" Mono.just(2).hasElement().single();",
|
||||
" Mono.just(2).hasElement().defaultIfEmpty(true);",
|
||||
" Mono.just(2).hasElement().switchIfEmpty(Mono.just(true));",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static com.google.common.collect.ImmutableList.toImmutableList;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"import reactor.core.publisher.Mono;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Flux.just(1).collect(toImmutableList());",
|
||||
" Flux.just(1).collect(toImmutableList());",
|
||||
" Flux.just(1).collect(toImmutableList());",
|
||||
"",
|
||||
" Mono.just(2).hasElement();",
|
||||
" Mono.just(2).hasElement();",
|
||||
" Mono.just(2).hasElement();",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
53
pom.xml
53
pom.xml
@@ -88,7 +88,6 @@
|
||||
-XX:+UseParallelGC
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.api=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
|
||||
@@ -359,14 +358,12 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
</dependency>
|
||||
<!-- Specified so that Renovate will file Maven upgrade PRs, which
|
||||
subsequently will cause `maven-enforcer-plugin` to require that
|
||||
developers build the project using the latest Maven release. -->
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-artifact</artifactId>
|
||||
<version>${version.maven}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-model</artifactId>
|
||||
<artifactId>maven-plugin-api</artifactId>
|
||||
<version>${version.maven}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -384,29 +381,6 @@
|
||||
<artifactId>checker-qual</artifactId>
|
||||
<version>3.24.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-classworlds</artifactId>
|
||||
<version>2.6.0</version>
|
||||
</dependency>
|
||||
<!-- XXX: This one is declared only to appease the
|
||||
`license-maven-plugin`. File a PR upstream to pull in a more recent
|
||||
version. -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-container-default</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-utils</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.gaul</groupId>
|
||||
<artifactId>modernizer-maven-plugin</artifactId>
|
||||
<version>2.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-core</artifactId>
|
||||
@@ -836,11 +810,6 @@
|
||||
<artifactId>auto-service</artifactId>
|
||||
<version>${version.auto-service}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
<version>${version.auto-value}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-beta-checker</artifactId>
|
||||
@@ -1139,7 +1108,6 @@
|
||||
non-distributed (i.e. Picnic-internal) deployable
|
||||
artifacts (i.e. web services). -->
|
||||
<includedLicense>Apache-2.0</includedLicense>
|
||||
<includedLicense>BSD-2-Clause</includedLicense>
|
||||
<includedLicense>BSD-3-Clause</includedLicense>
|
||||
<includedLicense>CC0-1.0</includedLicense>
|
||||
<includedLicense>CDDL-1.1</includedLicense>
|
||||
@@ -1167,11 +1135,6 @@
|
||||
| The Apache License, Version 2.0
|
||||
| The Apache Software License, Version 2.0
|
||||
</licenseMerge>
|
||||
<licenseMerge>
|
||||
<!-- -->
|
||||
BSD-2-Clause
|
||||
| The BSD License
|
||||
</licenseMerge>
|
||||
<licenseMerge>
|
||||
<!-- -->
|
||||
BSD-3-Clause
|
||||
@@ -1565,12 +1528,6 @@
|
||||
Prone bug pattern checkers, so we enable
|
||||
all and then selectively deactivate some. -->
|
||||
-XepAllDisabledChecksAsWarnings
|
||||
<!-- Some generated classes violate Error
|
||||
Prone bug patterns. We cannot in all cases
|
||||
avoid that, so we simply tell Error Prone
|
||||
not to warn about generated code. -->
|
||||
-XepDisableWarningsInGeneratedCode
|
||||
-XepExcludedPaths:\Q${project.build.directory}${file.separator}\E.*
|
||||
<!-- We don't target Android. -->
|
||||
-Xep:AndroidJdkLibsChecker:OFF
|
||||
<!-- XXX: Enable this once we open-source
|
||||
@@ -1785,8 +1742,6 @@
|
||||
<arg>--add-exports</arg>
|
||||
<arg>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||
<arg>--add-exports</arg>
|
||||
<arg>jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
|
||||
<arg>--add-exports</arg>
|
||||
<arg>jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||
<arg>--add-exports</arg>
|
||||
<arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||
|
||||
Reference in New Issue
Block a user