Compare commits

..

15 Commits

Author SHA1 Message Date
Stephan Schroevers
8ced020ba6 Post rebase fixes 2022-11-05 17:21:46 +01:00
Stephan Schroevers
389928ce43 Tweaks 2022-11-05 16:53:45 +01:00
Rick Ossendrijver
d88f226b30 Remove the TEXT_MATCH param from doTest 2022-11-05 16:52:02 +01:00
Rick Ossendrijver
ba91c6bed7 Suggestions 2022-11-05 16:52:02 +01:00
Nathan Kooij
85f402b089 Simplify by only working with longs 2022-11-05 16:52:02 +01:00
Nathan Kooij
ad8c0a472c Fix most warnings 2022-11-05 16:52:02 +01:00
Nathan Kooij
0af127652e Use a sorted set to derive the ceiling 2022-11-05 16:52:02 +01:00
Nathan Kooij
cede5e451b Assorted cleanup 2022-11-05 16:52:02 +01:00
Nathan Kooij
134895090f Support banned fields 2022-11-05 16:52:02 +01:00
Nathan Kooij
226bfd0cee Handle implicit value attribute case 2022-11-05 16:52:02 +01:00
Nathan Kooij
89a3c605fe Support multiple attributes per annotation 2022-11-05 16:52:02 +01:00
Nathan Kooij
63273a2609 Some cleanup of simplifying code 2022-11-05 16:52:02 +01:00
Nathan Kooij
79768a2428 Notes and random things 2022-11-05 16:52:02 +01:00
Nathan Kooij
dd8d094b5a Suggest a fix 2022-11-05 16:52:02 +01:00
Nathan Kooij
4830b5b2cd Extract TimeUnit, Number value, and determine a simplification 2022-11-05 16:52:02 +01:00
53 changed files with 1018 additions and 2551 deletions

View File

@@ -37,7 +37,7 @@ jobs:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.8.0
uses: actions/setup-java@v3.6.0
with:
java-version: ${{ matrix.jdk }}
distribution: ${{ matrix.distribution }}

View File

@@ -13,12 +13,12 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
- uses: ruby/setup-ruby@v1.126.0
- uses: ruby/setup-ruby@v1.120.0
with:
working-directory: ./website
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@v2.1.3
uses: actions/configure-pages@v2.1.2
- name: Generate documentation
run: ./generate-docs.sh
- name: Build website with Jekyll
@@ -30,7 +30,7 @@ jobs:
# "Refaster rules" terminology on our website and in the code.
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.5
uses: actions/upload-pages-artifact@v1.0.4
with:
path: ./website/_site
deploy:
@@ -46,4 +46,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1.2.3
uses: actions/deploy-pages@v1.2.2

View File

@@ -1,37 +0,0 @@
# Performs mutation testing analysis on the files changed by a pull request and
# uploads the results. The associated PR is subsequently updated by the
# `pitest-update-pr.yml` workflow. See https://blog.pitest.org/oss-pitest-pr/
# for details.
name: "Mutation testing"
on:
pull_request:
permissions:
contents: read
jobs:
analyze-pr:
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Run Pitest
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
# analyzes lines changed in the associated pull request, as GitHub
# exposes the changes unique to the PR as a single commit on top of the
# target branch. See https://blog.pitest.org/pitest-pr-setup for
# details.
run: mvn test pitest:mutationCoverage -DargLine.xmx=2048m -Dverification.skip -Dfeatures="+GIT(from[HEAD~1]), +gitci"
- name: Aggregate Pitest reports
run: mvn pitest-git:aggregate -DkilledEmoji=":tada:" -DmutantEmoji=":zombie:" -DtrailingText="Mutation testing report by [Pitest](https://pitest.org/). Review any surviving mutants by inspecting the line comments under [_Files changed_](${{ github.event.number }}/files)."
- name: Upload Pitest reports as artifact
uses: actions/upload-artifact@v3.1.1
with:
name: pitest-reports
path: ./target/pit-reports-ci

View File

@@ -1,35 +0,0 @@
# Updates a pull request based on the corresponding mutation testing analysis
# performed by the `pitest-analyze-pr.yml` workflow. See
# https://blog.pitest.org/oss-pitest-pr/ for details.
name: "Mutation testing: post results"
on:
workflow_run:
workflows: ["Mutation testing"]
types:
- completed
permissions:
actions: read
checks: write
contents: read
pull-requests: write
jobs:
update-pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Download Pitest analysis artifact
uses: dawidd6/action-download-artifact@v2.24.2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: pitest-reports
path: ./target/pit-reports-ci
- name: Update PR
run: mvn -DrepoToken="${{ secrets.GITHUB_TOKEN }}" pitest-github:updatePR

View File

@@ -10,7 +10,7 @@
},
{
"matchPackagePatterns": [
"^ruby\\/setup-ruby$"
"^com\\.palantir\\.baseline:baseline-error-prone$"
],
"schedule": "* * 1 * *"
}

View File

@@ -166,7 +166,7 @@ Some other commands one may find relevant:
- `mvn fmt:format` formats the code using
[`google-java-format`][google-java-format].
- `./run-mutation-tests.sh` runs mutation tests using [Pitest][pitest]. The
- `./run-mutation-tests.sh` runs mutation tests using [PIT][pitest]. The
results can be reviewed by opening the respective
`target/pit-reports/index.html` files. For more information check the [PIT
Maven plugin][pitest-maven].

View File

@@ -1,7 +0,0 @@
# Arcmutate license for Error Prone Support, requested by sending an email to
# support@arcmutate.com.
expires=07/11/2023
keyVersion=1
signature=MhZxMbnO6UovNfllM0JuVWkZyvRT3/G5o/uT0Mm36c7200VpZNVu03gTAGivnl9W5RzvZhfpIHccuQ5ctjQkrqhsFSrl4fyqPqu3y5V2fsHIdFXP/G72EGj6Kay9ndLpaEHalqE0bEwxdnHMzEYq5y3O9vUPv8MhUl57xk+rvBo\=
packages=tech.picnic.errorprone.*
type=OSSS

View File

@@ -15,6 +15,7 @@ import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
@@ -23,7 +24,6 @@ import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
@AutoService(BugChecker.class)
@@ -62,6 +62,6 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
* leave flagging the unused import to Error Prone's `RemoveUnusedImports` check.
*/
AnnotationTree annotation = Iterables.getOnlyElement(annotations);
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
return describeMatch(annotation, SuggestedFix.delete(annotation));
}
}

View File

@@ -14,6 +14,7 @@ import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
@@ -21,7 +22,6 @@ import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@@ -55,7 +55,7 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
return Description.NO_MATCH;
}
return describeMatch(tree, SourceCode.deleteWithTrailingWhitespace(tree, state));
return describeMatch(tree, SuggestedFix.delete(tree));
}
private static boolean isInPossibleTestHelperClass(VisitorState state) {

View File

@@ -1,82 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.hasMethod;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ClassTree;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} that flags non-final and non package-private JUnit test class declarations,
* unless abstract.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Non-abstract JUnit test classes should be declared package-private and final",
linkType = CUSTOM,
link = BUG_PATTERNS_BASE_URL + "JUnitClassModifiers",
severity = SUGGESTION,
tags = STYLE)
public final class JUnitClassModifiers extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ClassTree> HAS_SPRING_CONFIGURATION_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.springframework.context.annotation.Configuration"),
hasMetaAnnotation("org.springframework.context.annotation.Configuration")));
private static final Matcher<ClassTree> TEST_CLASS_WITH_INCORRECT_MODIFIERS =
allOf(
hasMethod(TEST_METHOD),
not(hasModifier(Modifier.ABSTRACT)),
anyOf(
hasModifier(Modifier.PRIVATE),
hasModifier(Modifier.PROTECTED),
hasModifier(Modifier.PUBLIC),
allOf(not(hasModifier(Modifier.FINAL)), not(HAS_SPRING_CONFIGURATION_ANNOTATION))));
/** Instantiates a new {@link JUnitClassModifiers} instance. */
public JUnitClassModifiers() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
if (!TEST_CLASS_WITH_INCORRECT_MODIFIERS.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(
tree.getModifiers(),
state,
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC))
.ifPresent(fixBuilder::merge);
if (!HAS_SPRING_CONFIGURATION_ANNOTATION.matches(tree, state)) {
SuggestedFixes.addModifiers(tree, state, Modifier.FINAL).ifPresent(fixBuilder::merge);
}
return describeMatch(tree, fixBuilder.build());
}
}

View File

@@ -13,8 +13,6 @@ import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
@@ -27,13 +25,18 @@ import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
import javax.lang.model.element.Name;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
@@ -61,6 +64,20 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
Matchers.not(hasModifier(Modifier.FINAL)),
Matchers.not(hasModifier(Modifier.PRIVATE)),
enclosingClass(hasModifier(Modifier.ABSTRACT))));
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
private static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
/** Instantiates a new {@link JUnitMethodDeclaration} instance. */
public JUnitMethodDeclaration() {}
@@ -125,7 +142,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
if (MoreASTHelpers.methodExistsInEnclosingClass(methodName, state)) {
if (isMethodInEnclosingClass(methodName, state)) {
return Optional.of(
String.format("a method named `%s` already exists in this class", methodName));
}
@@ -141,6 +158,15 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
return Optional.empty();
}
private static boolean isMethodInEnclosingClass(String methodName, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.map(MethodTree::getName)
.map(Name::toString)
.anyMatch(methodName::equals);
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
@@ -162,4 +188,18 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
.map(name -> Character.toLowerCase(name.charAt(0)) + name.substring(1))
.filter(name -> !Character.isDigit(name.charAt(0)));
}
// XXX: Move to a `MoreMatchers` utility class.
private static Matcher<AnnotationTree> hasMetaAnnotation(String annotationClassName) {
TypePredicate typePredicate = hasAnnotation(annotationClassName);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Move to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -0,0 +1,350 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags annotations with time attributes that can be written more
* concisely.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Simplifies annotations which express an amount of time using a `TimeUnit`",
link = BUG_PATTERNS_BASE_URL + "SimplifyTimeAnnotation",
linkType = CUSTOM,
severity = WARNING,
tags = SIMPLIFICATION)
public final class SimplifyTimeAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
createAnnotationAttributeMatcher();
/** Instantiates a new {@link SimplifyTimeAnnotationCheck} instance. */
public SimplifyTimeAnnotationCheck() {}
@Override
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState state) {
ImmutableList<ExpressionTree> arguments =
ARGUMENT_SELECTOR.extractMatchingArguments(annotationTree).collect(toImmutableList());
if (arguments.isEmpty()) {
return Description.NO_MATCH;
}
return trySimplification(annotationTree, arguments, state)
.map(fix -> describeMatch(annotationTree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional<Fix> trySimplification(
AnnotationTree annotation, ImmutableList<ExpressionTree> arguments, VisitorState state) {
checkArgument(!arguments.isEmpty());
AnnotationDescriptor annotationDescriptor =
AnnotationDescriptor.from(getAnnotationFqcn(annotation));
if (containsAnyAttributeOf(annotation, annotationDescriptor.bannedFields)) {
return Optional.empty();
}
ImmutableMap<String, ExpressionTree> indexedAttributes =
Maps.uniqueIndex(
arguments,
expr ->
ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
.getSimpleName()
.toString());
TimeUnit currentTimeUnit =
getTimeUnit(annotation, annotationDescriptor.timeUnitField, indexedAttributes);
ImmutableMap<String, Number> timeValues =
annotationDescriptor.timeFields.stream()
.map(field -> Map.entry(field, getValue(field, indexedAttributes)))
.filter(entry -> entry.getValue().isPresent())
.collect(toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().orElseThrow()));
Map<String, TimeSimplifier.Simplification> simplifications =
Maps.transformValues(
Maps.filterValues(
Maps.transformEntries(
timeValues, (field, value) -> trySimplify(value, currentTimeUnit)),
Optional::isPresent),
Optional::orElseThrow);
// Some could not be simplified, and since the unit is shared, the others can't either.
if (simplifications.size() != timeValues.size()) {
return Optional.empty();
}
// The annotation is of the form `@Annotation(v)` or `@Annotation(value = v)`. For the former we
// must synthesize the entire annotation, but this is OK for the latter, too.
if (indexedAttributes.size() == 1 && simplifications.containsKey("value")) {
TimeSimplifier.Simplification simplification = simplifications.get("value");
return Optional.of(
getImplicitValueAttributeFix(
annotation,
simplification.value,
annotationDescriptor.timeUnitField,
simplification.timeUnit,
state));
}
// Since each might have a different simplification possible, check the common unit.
// Since we only get simplifications iff it's possible, and we check that all can be simplified,
// we don't need to check if this equals `currentTimeUnit`.
TimeUnit commonUnit =
findCommonUnit(
ImmutableSet.copyOf(
Maps.transformValues(simplifications, simplification -> simplification.timeUnit)
.values()));
return getExplicitAttributesFix(
annotation, simplifications, annotationDescriptor.timeUnitField, commonUnit, state);
}
private static boolean containsAnyAttributeOf(
AnnotationTree annotation, ImmutableSet<String> attributes) {
return annotation.getArguments().stream()
.map(
expr ->
expr.getKind() == Tree.Kind.ASSIGNMENT
? ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
.getSimpleName()
.toString()
: "value")
.anyMatch(attributes::contains);
}
private static Fix getImplicitValueAttributeFix(
AnnotationTree annotation,
long newValue,
String timeUnitField,
TimeUnit newTimeUnit,
VisitorState state) {
String synthesizedAnnotation =
SourceCode.treeToString(annotation, state)
.replaceFirst(
"\\(.+\\)",
String.format("(value=%s, %s=%s)", newValue, timeUnitField, newTimeUnit.name()));
return SuggestedFix.builder()
.replace(annotation, synthesizedAnnotation)
.addStaticImport(TimeUnit.class.getName() + '.' + newTimeUnit.name())
.build();
}
private static Optional<Fix> getExplicitAttributesFix(
AnnotationTree annotation,
Map<String, TimeSimplifier.Simplification> simplifications,
String timeUnitField,
TimeUnit newUnit,
VisitorState state) {
return simplifications.entrySet().stream()
.map(
simplificationEntry ->
SuggestedFixes.updateAnnotationArgumentValues(
annotation, state, timeUnitField, ImmutableList.of(newUnit.name()))
.merge(
SuggestedFixes.updateAnnotationArgumentValues(
annotation,
state,
simplificationEntry.getKey(),
ImmutableList.of(
String.valueOf(simplificationEntry.getValue().toUnit(newUnit))))))
.reduce(SuggestedFix.Builder::merge)
.map(builder -> builder.addStaticImport(TimeUnit.class.getName() + '.' + newUnit.name()))
.map(SuggestedFix.Builder::build);
}
private static String getAnnotationFqcn(AnnotationTree annotation) {
return ASTHelpers.getSymbol(annotation).getQualifiedName().toString();
}
private static Optional<Number> getValue(
String field, ImmutableMap<String, ExpressionTree> indexedArguments) {
return Optional.ofNullable(indexedArguments.get(field))
.filter(AssignmentTree.class::isInstance)
.map(AssignmentTree.class::cast)
.map(AssignmentTree::getExpression)
.map(expr -> ASTHelpers.constValue(expr, Number.class));
}
private static TimeUnit getTimeUnit(
AnnotationTree annotation,
String field,
ImmutableMap<String, ExpressionTree> indexedArguments) {
VarSymbol symbol =
Optional.ofNullable(indexedArguments.get(field))
.map(
argumentTree ->
(VarSymbol)
ASTHelpers.getSymbol(((AssignmentTree) argumentTree).getExpression()))
.orElseGet(() -> getDefaultTimeUnit(annotation, field));
return TimeUnit.valueOf(symbol.getQualifiedName().toString());
}
private static VarSymbol getDefaultTimeUnit(AnnotationTree annotation, String argument) {
Scope scope = ASTHelpers.getSymbol(annotation).members();
MethodSymbol argumentSymbol =
(MethodSymbol)
Iterables.getOnlyElement(
ASTHelpers.scope(scope)
.getSymbols(symbol -> symbol.getQualifiedName().contentEquals(argument)));
return (VarSymbol)
requireNonNull(argumentSymbol.getDefaultValue(), "Default value missing").getValue();
}
private static AnnotationAttributeMatcher createAnnotationAttributeMatcher() {
ImmutableList<String> toMatch =
Arrays.stream(AnnotationDescriptor.values())
.flatMap(
annotation ->
annotation.timeFields.stream().map(field -> annotation.fqcn + '#' + field))
.collect(toImmutableList());
return AnnotationAttributeMatcher.create(Optional.of(toMatch), ImmutableList.of());
}
private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
checkArgument(
value instanceof Integer || value instanceof Long,
"Only time expressed as an integer or long can be simplified");
return TimeSimplifier.simplify(value.longValue(), unit);
}
private static TimeUnit findCommonUnit(ImmutableSet<TimeUnit> units) {
return ImmutableSortedSet.copyOf(units).first();
}
private enum AnnotationDescriptor {
JUNIT_TIMEOUT("org.junit.jupiter.api.Timeout", ImmutableSet.of("value"), "unit"),
SPRING_SCHEDULED(
"org.springframework.scheduling.annotation.Scheduled",
ImmutableSet.of("fixedDelay", "fixedRate", "initialDelay"),
"timeUnit",
ImmutableSet.of("fixedDelayString", "fixedRateString", "initialDelayString"));
/** The fully-qualified class name of the annotation to simplify. */
private final String fqcn;
/** The attributes containing a value of time. */
private final ImmutableSet<String> timeFields;
/** The attribute containing the time unit. */
private final String timeUnitField;
/** The set of attributes that cause the check to back off. */
private final ImmutableSet<String> bannedFields;
AnnotationDescriptor(String fqcn, ImmutableSet<String> timeFields, String timeUnitField) {
this(fqcn, timeFields, timeUnitField, ImmutableSet.of());
}
AnnotationDescriptor(
String fqcn,
ImmutableSet<String> timeFields,
String timeUnitField,
ImmutableSet<String> bannedFields) {
this.fqcn = fqcn;
this.timeFields = timeFields;
this.timeUnitField = timeUnitField;
this.bannedFields = bannedFields;
}
public static AnnotationDescriptor from(String fqcn) {
return Arrays.stream(values())
.filter(annotation -> annotation.fqcn.equals(fqcn))
.findFirst()
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Unknown enum constant: %s.%s",
AnnotationDescriptor.class.getName(), fqcn)));
}
}
/** Utility class to help simplify time expressions. */
private static final class TimeSimplifier {
private static final ImmutableSortedSet<TimeUnit> TIME_UNITS =
ImmutableSortedSet.copyOf(TimeUnit.values());
/**
* Returns a {@link Simplification} (iff possible) that describes how the {@code originalValue}
* and {@code originalUnit} can be simplified using a larger {@link TimeUnit}.
*/
static Optional<Simplification> simplify(long originalValue, TimeUnit originalUnit) {
return descendingLargerUnits(originalUnit).stream()
.flatMap(unit -> trySimplify(originalValue, originalUnit, unit))
.findFirst();
}
private static Stream<Simplification> trySimplify(
long originalValue, TimeUnit originalUnit, TimeUnit unit) {
long converted = unit.convert(originalValue, originalUnit);
// Check whether we lose any precision by checking whether we can convert back.
return originalValue == originalUnit.convert(converted, unit)
? Stream.of(new Simplification(converted, unit))
: Stream.empty();
}
/**
* Returns all time units that represent a larger amount of time than {@code unit}, in
* descending order.
*/
private static ImmutableSortedSet<TimeUnit> descendingLargerUnits(TimeUnit unit) {
return TIME_UNITS.tailSet(unit, /* inclusive= */ false).descendingSet();
}
/** Represents a simplification in terms of the new value and new unit. */
private static final class Simplification {
private final long value;
private final TimeUnit timeUnit;
Simplification(long value, TimeUnit timeUnit) {
this.value = value;
this.timeUnit = timeUnit;
}
/**
* Converts the value with the unit represented by this simplification to an equivalent value
* in the given {@code unit}.
*/
public long toUnit(TimeUnit unit) {
return unit.convert(value, timeUnit);
}
}
}
}

View File

@@ -1,70 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
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.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags calls to {@link String#toLowerCase()} and {@link
* String#toUpperCase()}, as these methods implicitly rely on the environment's default locale.
*/
// XXX: Also flag `String::toLowerCase` and `String::toUpperCase` method references. For these cases
// the suggested fix should introduce a lambda expression with a parameter of which the name does
// not coincide with the name of an existing variable name. Such functionality should likely be
// introduced in a utility class.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Specify a `Locale` when calling `String#to{Lower,Upper}Case`",
link = BUG_PATTERNS_BASE_URL + "StringCaseLocaleUsage",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class StringCaseLocaleUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DEFAULT_LOCALE_CASE_CONVERSION =
instanceMethod()
.onExactClass(String.class.getName())
.namedAnyOf("toLowerCase", "toUpperCase")
.withNoParameters();
/** Instantiates a new {@link StringCaseLocaleUsage} instance. */
public StringCaseLocaleUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!DEFAULT_LOCALE_CASE_CONVERSION.matches(tree, state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(suggestLocale(tree, "Locale.ROOT", state))
.addFix(suggestLocale(tree, "Locale.getDefault()", state))
.build();
}
private static Fix suggestLocale(MethodInvocationTree tree, String locale, VisitorState state) {
// XXX: The logic that replaces the first parenthesis assumes that `tree` does not have a source
// code representation such as `str.toLowerCase/* Some comment with parens (). */()`. In such a
// case the comment, rather than the method invocation arguments, will be modified. Implement a
// generic solution for this.
return SuggestedFix.builder()
.addImport("java.util.Locale")
.replace(tree, SourceCode.treeToString(tree, state).replaceFirst("\\(", '(' + locale))
.build();
}
}

View File

@@ -1,49 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
/**
* A collection of helper methods for working with the AST.
*
* <p>These methods are additions to the ones found in {@link
* com.google.errorprone.util.ASTHelpers}.
*/
public final class MoreASTHelpers {
private MoreASTHelpers() {}
/**
* Finds methods with the specified name in given the {@link VisitorState}'s current enclosing
* class.
*
* @param methodName The method name to search for.
* @param state The {@link VisitorState} from which to derive the enclosing class of interest.
* @return The {@link MethodTree}s of the methods with the given name in the enclosing class.
*/
public static ImmutableList<MethodTree> findMethods(CharSequence methodName, VisitorState state) {
ClassTree clazz = state.findEnclosing(ClassTree.class);
checkArgument(clazz != null, "Visited node is not enclosed by a class");
return clazz.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(method -> method.getName().contentEquals(methodName))
.collect(toImmutableList());
}
/**
* Determines whether there are any methods with the specified name in given the {@link
* VisitorState}'s current enclosing class.
*
* @param methodName The method name to search for.
* @param state The {@link VisitorState} from which to derive the enclosing class of interest.
* @return Whether there are any methods with the given name in the enclosing class.
*/
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}
}

View File

@@ -1,81 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.Objects.requireNonNullElse;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.matchers.AnnotationMatcherUtils;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import org.jspecify.nullness.Nullable;
/**
* A collection of JUnit-specific helper methods and {@link Matcher}s.
*
* <p>These constants and methods are additions to the ones found in {@link
* com.google.errorprone.matchers.JUnitMatchers}.
*/
public final class MoreJUnitMatchers {
/** Matches JUnit Jupiter test methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
/** Matches JUnit Jupiter setup and teardown methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
/**
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation.
*/
public static final MultiMatcher<MethodTree, AnnotationTree> HAS_METHOD_SOURCE =
annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource"));
private MoreJUnitMatchers() {}
/**
* Returns the names of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method names.
* @return One or more value factory names.
*/
static ImmutableSet<String> getMethodSourceFactoryNames(
AnnotationTree methodSourceAnnotation, MethodTree method) {
String methodName = method.getName().toString();
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value");
if (!(value instanceof NewArrayTree)) {
return ImmutableSet.of(toMethodSourceFactoryName(value, methodName));
}
return ((NewArrayTree) value)
.getInitializers().stream()
.map(name -> toMethodSourceFactoryName(name, methodName))
.collect(toImmutableSet());
}
private static String toMethodSourceFactoryName(
@Nullable ExpressionTree tree, String annotatedMethodName) {
return requireNonNullElse(
Strings.emptyToNull(ASTHelpers.constValue(tree, String.class)), annotatedMethodName);
}
}

View File

@@ -1,39 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.tools.javac.code.Symbol;
/**
* A collection of general-purpose {@link Matcher}s.
*
* <p>These methods are additions to the ones found in {@link Matchers}.
*/
public final class MoreMatchers {
private MoreMatchers() {}
/**
* Returns a {@link Matcher} that determines whether a given {@link AnnotationTree} has a
* meta-annotation of the specified type.
*
* @param <T> The type of tree to match against.
* @param annotationType The binary type name of the annotation (e.g.
* "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName")
* @return A {@link Matcher} that matches trees with the specified meta-annotation.
*/
public static <T extends AnnotationTree> Matcher<T> hasMetaAnnotation(String annotationType) {
TypePredicate typePredicate = hasAnnotation(annotationType);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Consider moving to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -1,21 +1,14 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.sun.tools.javac.util.Position.NOPOS;
import com.google.common.base.CharMatcher;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
* AST nodes.
*/
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
public final class SourceCode {
/** The complement of {@link CharMatcher#whitespace()}. */
private static final CharMatcher NON_WHITESPACE_MATCHER = CharMatcher.whitespace().negate();
private SourceCode() {}
/**
@@ -31,32 +24,4 @@ public final class SourceCode {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
/**
* Creates a {@link SuggestedFix} for the deletion of the given {@link Tree}, including any
* whitespace that follows it.
*
* <p>Removing trailing whitespace may prevent the introduction of an empty line at the start of a
* code block; such empty lines are not removed when formatting the code using Google Java Format.
*
* @param tree The AST node of interest.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
* found.
* @return A non-{@code null} {@link SuggestedFix} similar to one produced by {@link
* SuggestedFix#delete(Tree)}.
*/
public static SuggestedFix deleteWithTrailingWhitespace(Tree tree, VisitorState state) {
CharSequence sourceCode = state.getSourceCode();
int endPos = state.getEndPosition(tree);
if (sourceCode == null || endPos == NOPOS) {
/* We can't identify the trailing whitespace; delete just the tree. */
return SuggestedFix.delete(tree);
}
int whitespaceEndPos = NON_WHITESPACE_MATCHER.indexIn(sourceCode, endPos);
return SuggestedFix.replace(
((DiagnosticPosition) tree).getStartPosition(),
whitespaceEndPos == -1 ? sourceCode.length() : whitespaceEndPos,
"");
}
}

View File

@@ -1,148 +1,16 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@OnlineDocumentation
final class AssertJMapRules {
private AssertJMapRules() {}
// XXX: Reduce boilerplate using a `Matcher` that identifies "empty" instances.
static final class AbstractMapAssertIsEmpty<K, V> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
Refaster.anyOf(
mapAssert.containsExactlyEntriesOf(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.isEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.containsOnlyKeys(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
mapAssert.containsExactly(),
mapAssert.containsOnly(),
mapAssert.containsOnlyKeys());
}
@AfterTemplate
void after(AbstractMapAssert<?, ?, K, V> mapAssert) {
mapAssert.isEmpty();
}
}
static final class AssertThatMapIsEmpty<K, V> {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@BeforeTemplate
void before2(Map<K, V> map) {
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).isEmpty();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
}
static final class AbstractMapAssertIsNotEmpty<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>()));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEmpty();
}
}
static final class AssertThatMapIsNotEmpty<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map) {
return Refaster.anyOf(
assertThat(map.isEmpty()).isFalse(),
assertThat(map.size()).isNotEqualTo(0),
assertThat(map.size()).isPositive(),
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).isNotEmpty());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
}
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
@@ -166,83 +34,4 @@ final class AssertJMapRules {
return mapAssert.containsExactlyEntriesOf(ImmutableMap.of(key, value));
}
}
static final class AssertThatMapHasSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map, int length) {
return Refaster.anyOf(
assertThat(map.size()).isEqualTo(length),
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).hasSize(length));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
}
static final class AbstractMapAssertHasSameSizeAs<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.hasSize(map.size());
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.hasSameSizeAs(map);
}
}
static final class AssertThatMapContainsKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
}
static final class AssertThatMapDoesNotContainKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
}
static final class AssertThatMapContainsValue<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {
return assertThat(map.containsValue(value)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, V value) {
return assertThat(map).containsValue(value);
}
}
static final class AssertThatMapDoesNotContainValue<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {
return assertThat(map.containsValue(value)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, V value) {
return assertThat(map).doesNotContainValue(value);
}
}
}

View File

@@ -3,9 +3,12 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
@@ -19,7 +22,9 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -27,16 +32,19 @@ import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractComparableAssert;
import org.assertj.core.api.AbstractDoubleAssert;
import org.assertj.core.api.AbstractIntegerAssert;
import org.assertj.core.api.AbstractLongAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.IterableAssert;
import org.assertj.core.api.ListAssert;
import org.assertj.core.api.MapAssert;
@@ -558,8 +566,173 @@ final class AssertJRules {
// Map
//
// XXX: To match in all cases there'll need to be a `@BeforeTemplate` variant for each
// `assertThat` overload. Consider defining a `BugChecker` instead.
static final class AssertThatMapIsEmpty<K, V> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
Refaster.anyOf(
mapAssert.containsExactlyEntriesOf(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.isEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.containsOnlyKeys(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
mapAssert.containsExactly(),
mapAssert.containsOnly(),
mapAssert.containsOnlyKeys());
}
@AfterTemplate
void after(AbstractMapAssert<?, ?, K, V> mapAssert) {
mapAssert.isEmpty();
}
}
// XXX: Find a better name.
static final class AssertThatMapIsEmpty2<K, V> {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@BeforeTemplate
void before2(Map<K, V> map) {
assertThat(Refaster.anyOf(map.keySet(), map.values())).isEmpty();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
}
static final class AssertThatMapIsNotEmpty<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>()));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEmpty();
}
}
// XXX: Find a better name.
static final class AssertThatMapIsNotEmpty2<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map) {
return Refaster.anyOf(
assertThat(map.isEmpty()).isFalse(),
assertThat(map.size()).isNotEqualTo(0),
assertThat(map.size()).isPositive(),
assertThat(Refaster.anyOf(map.keySet(), map.values())).isNotEmpty());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
}
static final class AssertThatMapHasSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map, int length) {
return Refaster.anyOf(
assertThat(map.size()).isEqualTo(length),
assertThat(Refaster.anyOf(map.keySet(), map.values())).hasSize(length));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
}
static final class AssertThatMapsHaveSameSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1)
.hasSize(Refaster.anyOf(map2.size(), map2.keySet().size(), map2.values().size()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1).hasSameSizeAs(map2);
}
}
// XXX: Should also add a rule (elsewhere) to simplify `map.keySet().contains(key)`.
static final class AssertThatMapContainsKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
}
static final class AssertThatMapDoesNotContainKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
}
static final class AssertThatMapContainsEntry<K, V> {
@BeforeTemplate
ObjectAssert<?> before(Map<K, V> map, K key, V value) {

View File

@@ -19,8 +19,11 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
@@ -70,6 +73,32 @@ final class AssortedRules {
}
}
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
// not allow an empty non-EnumMap to be provided.
static final class CreateEnumMap<K extends Enum<K>, V> {
@BeforeTemplate
Map<K, V> before() {
return new HashMap<>();
}
@AfterTemplate
Map<K, V> after() {
return new EnumMap<>(Refaster.<K>clazz());
}
}
static final class MapGetOrNull<K, V, L> {
@BeforeTemplate
@Nullable V before(Map<K, V> map, L key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable V after(Map<K, V> map, L key) {
return map.get(key);
}
}
/**
* Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link
* ImmutableSet#toImmutableSet()} and produces a more compact object.
@@ -195,6 +224,32 @@ final class AssortedRules {
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapKeyStream<K, V> {
@BeforeTemplate
Stream<K> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getKey);
}
@AfterTemplate
Stream<K> after(Map<K, V> map) {
return map.keySet().stream();
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapValueStream<K, V> {
@BeforeTemplate
Stream<V> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getValue);
}
@AfterTemplate
Stream<V> after(Map<K, V> map) {
return map.values().stream();
}
}
/** Prefer {@link Splitter#splitToStream(CharSequence)} over less efficient alternatives. */
static final class SplitToStream {
@BeforeTemplate

View File

@@ -50,18 +50,17 @@ final class BigDecimalRules {
}
}
/** Prefer {@link BigDecimal#valueOf(double)} over the associated constructor. */
// XXX: Ideally we also rewrite `new BigDecimal("<some-integer-value>")` in cases where the
// specified number can be represented as an `int` or `long`, but that requires a custom
// `BugChecker`.
static final class BigDecimalValueOf {
/** Prefer {@link BigDecimal#valueOf(long)} over the associated constructor. */
// XXX: Ideally we'd also rewrite `BigDecimal.valueOf("<some-integer-value>")`, but it doesn't
// appear that's currently possible with Error Prone.
static final class BigDecimalFactoryMethod {
@BeforeTemplate
BigDecimal before(double value) {
BigDecimal before(long value) {
return new BigDecimal(value);
}
@AfterTemplate
BigDecimal after(double value) {
BigDecimal after(long value) {
return BigDecimal.valueOf(value);
}
}

View File

@@ -17,7 +17,6 @@ import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -405,19 +404,6 @@ final class CollectionRules {
}
}
/** Prefer {@link Collection#forEach(Consumer)} over more contrived alternatives. */
static final class CollectionForEach<T> {
@BeforeTemplate
void before(Collection<T> collection, Consumer<? super T> consumer) {
collection.stream().forEach(consumer);
}
@AfterTemplate
void after(Collection<T> collection, Consumer<? super T> consumer) {
collection.forEach(consumer);
}
}
// XXX: collection.stream().noneMatch(e -> e.equals(other))
// ^ This is !collection.contains(other). Do we already rewrite variations on this?
}

View File

@@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collections;
@@ -25,7 +24,6 @@ import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Comparator}s. */
@@ -39,10 +37,7 @@ final class ComparatorRules {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
T::compareTo,
comparing(Refaster.anyOf(identity(), v -> v)),
Collections.<T>reverseOrder(reverseOrder()),
Comparator.<T>reverseOrder().reversed());
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
}
@AfterTemplate
@@ -56,16 +51,11 @@ final class ComparatorRules {
static final class ReverseOrder<T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
Collections.reverseOrder(),
Collections.reverseOrder(T::compareTo),
Collections.<T>reverseOrder(naturalOrder()),
Comparator.<T>naturalOrder().reversed());
return Comparator.<T>naturalOrder().reversed();
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` if/when
// https://github.com/google/error-prone/pull/3584 is merged and released.
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return reverseOrder();
}
@@ -199,54 +189,15 @@ final class ComparatorRules {
}
}
/** Prefer {@link Comparable#compareTo(Object)}} over more verbose alternatives. */
static final class CompareTo<T extends Comparable<? super T>> {
@BeforeTemplate
int before(T value1, T value2) {
return Refaster.anyOf(
Comparator.<T>naturalOrder().compare(value1, value2),
Comparator.<T>reverseOrder().compare(value2, value1));
}
@AfterTemplate
int after(T value1, T value2) {
return value1.compareTo(value2);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class MinOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.min(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#min(Comparable, Comparable)}} over more verbose alternatives. */
static final class MinOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Refaster.anyOf(
value1.compareTo(value2) <= 0 ? value1 : value2,
value1.compareTo(value2) > 0 ? value2 : value1,
value2.compareTo(value1) < 0 ? value2 : value1,
value2.compareTo(value1) >= 0 ? value1 : value2,
Comparators.min(value1, value2, naturalOrder()),
Comparators.max(value1, value2, reverseOrder()),
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
}
@AfterTemplate
@@ -261,17 +212,12 @@ final class ComparatorRules {
static final class MinOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Refaster.anyOf(
cmp.compare(value1, value2) <= 0 ? value1 : value2,
cmp.compare(value1, value2) > 0 ? value2 : value1,
cmp.compare(value2, value1) < 0 ? value2 : value1,
cmp.compare(value2, value1) >= 0 ? value1 : value2,
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
}
@AfterTemplate
@@ -280,39 +226,15 @@ final class ComparatorRules {
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class MaxOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.max(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#max(Comparable, Comparable)}} over more verbose alternatives. */
static final class MaxOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Refaster.anyOf(
value1.compareTo(value2) >= 0 ? value1 : value2,
value1.compareTo(value2) < 0 ? value2 : value1,
value2.compareTo(value1) > 0 ? value2 : value1,
value2.compareTo(value1) <= 0 ? value1 : value2,
Comparators.max(value1, value2, naturalOrder()),
Comparators.min(value1, value2, reverseOrder()),
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
}
@AfterTemplate
@@ -327,17 +249,12 @@ final class ComparatorRules {
static final class MaxOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Refaster.anyOf(
cmp.compare(value1, value2) >= 0 ? value1 : value2,
cmp.compare(value1, value2) < 0 ? value2 : value1,
cmp.compare(value2, value1) > 0 ? value2 : value1,
cmp.compare(value2, value1) <= 0 ? value1 : value2,
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
}
@AfterTemplate

View File

@@ -1,121 +0,0 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Map} instances. */
@OnlineDocumentation
final class MapRules {
private MapRules() {}
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
// not allow an empty non-EnumMap to be provided.
static final class CreateEnumMap<K extends Enum<K>, V> {
@BeforeTemplate
Map<K, V> before() {
return new HashMap<>();
}
@AfterTemplate
Map<K, V> after() {
return new EnumMap<>(Refaster.<K>clazz());
}
}
static final class MapGetOrNull<K, V, T> {
@BeforeTemplate
@Nullable V before(Map<K, V> map, T key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable V after(Map<K, V> map, T key) {
return map.get(key);
}
}
/** Prefer {@link Map#isEmpty()} over more contrived alternatives. */
static final class MapIsEmpty<K, V> {
@BeforeTemplate
boolean before(Map<K, V> map) {
return Refaster.anyOf(map.keySet(), map.values(), map.entrySet()).isEmpty();
}
@AfterTemplate
boolean after(Map<K, V> map) {
return map.isEmpty();
}
}
/** Prefer {@link Map#size()} over more contrived alternatives. */
static final class MapSize<K, V> {
@BeforeTemplate
int before(Map<K, V> map) {
return Refaster.anyOf(map.keySet(), map.values(), map.entrySet()).size();
}
@AfterTemplate
int after(Map<K, V> map) {
return map.size();
}
}
/** Prefer {@link Map#containsKey(Object)} over more contrived alternatives. */
static final class MapContainsKey<K, V, T> {
@BeforeTemplate
boolean before(Map<K, V> map, T key) {
return map.keySet().contains(key);
}
@AfterTemplate
boolean after(Map<K, V> map, T key) {
return map.containsKey(key);
}
}
/** Prefer {@link Map#containsValue(Object)} over more contrived alternatives. */
static final class MapContainsValue<K, V, T> {
@BeforeTemplate
boolean before(Map<K, V> map, T value) {
return map.values().contains(value);
}
@AfterTemplate
boolean after(Map<K, V> map, T value) {
return map.containsValue(value);
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapKeyStream<K, V> {
@BeforeTemplate
Stream<K> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getKey);
}
@AfterTemplate
Stream<K> after(Map<K, V> map) {
return map.keySet().stream();
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapValueStream<K, V> {
@BeforeTemplate
Stream<V> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getValue);
}
@AfterTemplate
Stream<V> after(Map<K, V> map) {
return map.values().stream();
}
}
}

View File

@@ -26,7 +26,6 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jspecify.nullness.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -60,45 +59,6 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#empty()} over more contrived alternatives. */
static final class MonoEmpty<T> {
@BeforeTemplate
Mono<T> before() {
return Refaster.anyOf(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
@AfterTemplate
Mono<T> after() {
return Mono.empty();
}
}
/** Prefer {@link Mono#just(Object)} over more contrived alternatives. */
static final class MonoJust<T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.of(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.just(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Object)} over more contrived alternatives. */
static final class MonoJustOrEmpty<@Nullable T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.ofNullable(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.justOrEmpty(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Optional)} over more verbose alternatives. */
// XXX: If `optional` is a constant and effectively-final expression then the `Mono.defer` can be
// dropped. Should look into Refaster support for identifying this.
@@ -118,40 +78,6 @@ final class ReactorRules {
}
}
/**
* Try to avoid expressions of type {@code Optional<Mono<T>>}, but if you must map an {@link
* Optional} to this type, prefer using {@link Mono#just(Object)}.
*/
static final class OptionalMapMonoJust<T> {
@BeforeTemplate
Optional<Mono<T>> before(Optional<T> optional) {
return optional.map(Mono::justOrEmpty);
}
@AfterTemplate
Optional<Mono<T>> after(Optional<T> optional) {
return optional.map(Mono::just);
}
}
/**
* Prefer a {@link Mono#justOrEmpty(Optional)} and {@link Mono#switchIfEmpty(Mono)} chain over
* more contrived alternatives.
*
* <p>In particular, avoid mixing of the {@link Optional} and {@link Mono} APIs.
*/
static final class MonoFromOptionalSwitchIfEmpty<T> {
@BeforeTemplate
Mono<T> before(Optional<T> optional, Mono<T> mono) {
return optional.map(Mono::just).orElse(mono);
}
@AfterTemplate
Mono<T> after(Optional<T> optional, Mono<T> mono) {
return Mono.justOrEmpty(optional).switchIfEmpty(mono);
}
}
/**
* Prefer {@link Mono#zip(Mono, Mono)} over a chained {@link Mono#zipWith(Mono)}, as the former
* better conveys that the {@link Mono}s may be subscribed to concurrently, and generalizes to
@@ -338,32 +264,6 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#defaultIfEmpty(Object)} over more contrived alternatives. */
static final class MonoDefaultIfEmpty<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono, T object) {
return mono.switchIfEmpty(Mono.just(object));
}
@AfterTemplate
Mono<T> after(Mono<T> mono, T object) {
return mono.defaultIfEmpty(object);
}
}
/** Prefer {@link Flux#defaultIfEmpty(Object)} over more contrived alternatives. */
static final class FluxDefaultIfEmpty<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, T object) {
return flux.switchIfEmpty(Refaster.anyOf(Mono.just(object), Flux.just(object)));
}
@AfterTemplate
Flux<T> after(Flux<T> flux, T object) {
return flux.defaultIfEmpty(object);
}
}
/** Don't unnecessarily pass an empty publisher to {@link Mono#switchIfEmpty(Mono)}. */
static final class MonoSwitchIfEmptyOfEmptyPublisher<T> {
@BeforeTemplate
@@ -394,10 +294,7 @@ final class ReactorRules {
static final class FluxConcatMap<T, S> {
@BeforeTemplate
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
return Refaster.anyOf(
flux.flatMap(function, 1),
flux.flatMapSequential(function, 1),
flux.map(function).concatMap(identity()));
return Refaster.anyOf(flux.flatMap(function, 1), flux.flatMapSequential(function, 1));
}
@AfterTemplate
@@ -414,9 +311,7 @@ final class ReactorRules {
Function<? super T, ? extends Publisher<? extends S>> function,
int prefetch) {
return Refaster.anyOf(
flux.flatMap(function, 1, prefetch),
flux.flatMapSequential(function, 1, prefetch),
flux.map(function).concatMap(identity(), prefetch));
flux.flatMap(function, 1, prefetch), flux.flatMapSequential(function, 1, prefetch));
}
@AfterTemplate
@@ -664,8 +559,7 @@ final class ReactorRules {
static final class MonoFlux<T> {
@BeforeTemplate
Flux<T> before(Mono<T> mono) {
return Refaster.anyOf(
mono.flatMapMany(Mono::just), mono.flatMapMany(Flux::just), Flux.concat(mono));
return Flux.concat(mono);
}
@AfterTemplate
@@ -682,7 +576,9 @@ final class ReactorRules {
static final class MonoCollectToOptional<T> {
@BeforeTemplate
Mono<Optional<T>> before(Mono<T> mono) {
return mono.map(Optional::of).defaultIfEmpty(Optional.empty());
return Refaster.anyOf(
mono.map(Optional::of).defaultIfEmpty(Optional.empty()),
mono.map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
}
@AfterTemplate
@@ -718,32 +614,6 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#flatMap(Function)} over more contrived alternatives. */
static final class MonoFlatMap<S, T> {
@BeforeTemplate
Mono<T> before(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
return mono.map(function).flatMap(identity());
}
@AfterTemplate
Mono<T> after(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
return mono.flatMap(function);
}
}
/** Prefer {@link Mono#flatMapMany(Function)} over more contrived alternatives. */
static final class MonoFlatMapMany<S, T> {
@BeforeTemplate
Flux<T> before(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
return mono.map(function).flatMapMany(identity());
}
@AfterTemplate
Flux<T> after(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
return mono.flatMapMany(function);
}
}
/**
* Prefer {@link Flux#concatMapIterable(Function)} over alternatives that require an additional
* subscription.

View File

@@ -77,9 +77,6 @@ final class StreamRules {
* Prefer {@link Arrays#stream(Object[])} over {@link Stream#of(Object[])}, as the former is
* clearer.
*/
// XXX: Introduce a `Matcher` that identifies `Refaster.asVarargs(...)` invocations and annotate
// the `array` parameter as `@NotMatches(IsRefasterAsVarargs.class)`. Then elsewhere
// `@SuppressWarnings("StreamOfArray")` annotations can be dropped.
static final class StreamOfArray<T> {
@BeforeTemplate
Stream<T> before(T[] array) {

View File

@@ -94,11 +94,13 @@ final class AutowiredConstructorTest {
"",
"interface Container {",
" class A {",
"",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
"",
" B(String x) {}",
" }",
"}")

View File

@@ -76,18 +76,8 @@ final class EmptyMethodTest {
" void instanceMethod() {}",
"",
" static void staticMethod() {}",
"",
" static void staticMethodWithComment() {",
" /* Foo. */",
" }",
"}")
.addOutputLines(
"A.java",
"final class A {",
" static void staticMethodWithComment() {",
" /* Foo. */",
" }",
"}")
.addOutputLines("A.java", "final class A {}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,134 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitClassModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"Container.java",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.springframework.boot.test.context.TestConfiguration;",
"import org.springframework.context.annotation.Configuration;",
"",
"class Container {",
" final class FinalAndPackagePrivate {",
" @Test",
" void foo() {}",
" }",
"",
" final class FinalAndPackagePrivateWithCustomTestMethod {",
" @ParameterizedTest",
" void foo() {}",
" }",
"",
" public abstract class Abstract {",
" @Test",
" void foo() {}",
" }",
"",
" @Configuration",
" class WithConfigurationAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" @TestConfiguration",
" class WithConfigurationMetaAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" private final class Private {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" protected final class Protected {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" public final class Public {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" class NonFinal {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" class NonFinalWithCustomTestMethod {",
" @ParameterizedTest",
" void foo() {}",
" }",
"",
" @Configuration",
" // BUG: Diagnostic contains:",
" public class PublicWithConfigurationAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" @TestConfiguration",
" // BUG: Diagnostic contains:",
" protected class ProtectedWithConfigurationMetaAnnotation {",
" @Test",
" void foo() {}",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"import org.springframework.context.annotation.Configuration;",
"",
"public class A {",
" @Test",
" void foo() {}",
"",
" @Configuration",
" private static class B {",
" @Test",
" void bar() {}",
" }",
"}")
.addOutputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"import org.springframework.context.annotation.Configuration;",
"",
"final class A {",
" @Test",
" void foo() {}",
"",
" @Configuration",
" static class B {",
" @Test",
" void bar() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,146 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
public final class SimplifyTimeAnnotationCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(SimplifyTimeAnnotationCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(SimplifyTimeAnnotationCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.util.concurrent.TimeUnit;",
"import org.junit.jupiter.api.Timeout;",
"",
"interface A {",
" @Timeout(6)",
" A noSimplification();",
" // BUG: Diagnostic contains:",
" @Timeout(60)",
" A simple();",
" // BUG: Diagnostic contains:",
" @Timeout(value = 60 * 1000, unit = TimeUnit.MILLISECONDS)",
" A explicitUnit();",
"}")
.doTest();
}
@Test
void identificationBannedField() {
compilationTestHelper
.addSourceLines(
"A.java",
"import org.springframework.scheduling.annotation.Scheduled;",
"",
"interface A {",
" // BUG: Diagnostic contains:",
" @Scheduled(fixedDelay = 6_000)",
" A scheduledFixedDelay();",
"",
" @Scheduled(fixedDelay = 6_000, fixedRateString = \"\")",
" A bannedAttribute();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import org.junit.jupiter.api.Timeout;",
"import org.springframework.scheduling.annotation.Scheduled;",
"",
"interface A {",
" @Timeout(value = 60)",
" A simple();",
"",
" @Scheduled(fixedDelay = 6_000)",
" A scheduledFixedDelay();",
"",
" @Scheduled(fixedDelay = 5_000, initialDelay = 6_000, fixedRate = 7_000)",
" A scheduledMultiple();",
"",
" @Scheduled(fixedDelay = 60_000, initialDelay = 6_000, fixedRate = 7_000)",
" A scheduledCommonUnit();",
"",
" @Scheduled(fixedDelay = 5, initialDelay = 6_000, fixedRate = 7_000)",
" A scheduledNoSimplification();",
"}")
.addOutputLines(
"out/A.java",
"import static java.util.concurrent.TimeUnit.MINUTES;",
"import static java.util.concurrent.TimeUnit.SECONDS;",
"",
"import org.junit.jupiter.api.Timeout;",
"import org.springframework.scheduling.annotation.Scheduled;",
"",
"interface A {",
" @Timeout(value = 1, unit = MINUTES)",
" A simple();",
"",
" @Scheduled(timeUnit = SECONDS, fixedDelay = 6)",
" A scheduledFixedDelay();",
"",
" @Scheduled(timeUnit = SECONDS, fixedDelay = 5, initialDelay = 6, fixedRate = 7)",
" A scheduledMultiple();",
"",
" @Scheduled(timeUnit = SECONDS, fixedDelay = 60, initialDelay = 6, fixedRate = 7)",
" A scheduledCommonUnit();",
"",
" @Scheduled(fixedDelay = 5, initialDelay = 6_000, fixedRate = 7_000)",
" A scheduledNoSimplification();",
"}")
.doTest();
}
@Test
void replacementValueOnly() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import org.junit.jupiter.api.Timeout;",
"",
"interface A {",
" @Timeout(60)",
" A simple();",
"}")
.addOutputLines(
"out/A.java",
"import static java.util.concurrent.TimeUnit.MINUTES;",
"",
"import org.junit.jupiter.api.Timeout;",
"",
"interface A {",
" @Timeout(value = 1, unit = MINUTES)",
" A simple();",
"}")
.doTest();
}
@Test
void replacementFqcn() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"interface A {",
" @org.junit.jupiter.api.Timeout(60)",
" A simple();",
"}")
.addOutputLines(
"out/A.java",
"import static java.util.concurrent.TimeUnit.MINUTES;",
"",
"interface A {",
" @org.junit.jupiter.api.Timeout(value = 1, unit = MINUTES)",
" A simple();",
"}")
.doTest();
}
}

View File

@@ -19,7 +19,6 @@ final class SpringMvcAnnotationTest {
"import static org.springframework.web.bind.annotation.RequestMethod.DELETE;",
"import static org.springframework.web.bind.annotation.RequestMethod.GET;",
"import static org.springframework.web.bind.annotation.RequestMethod.HEAD;",
"import static org.springframework.web.bind.annotation.RequestMethod.PATCH;",
"import static org.springframework.web.bind.annotation.RequestMethod.POST;",
"import static org.springframework.web.bind.annotation.RequestMethod.PUT;",
"",
@@ -50,7 +49,7 @@ final class SpringMvcAnnotationTest {
" @RequestMapping(method = {DELETE})",
" A delete();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PATCH})",
" @RequestMapping(method = {org.springframework.web.bind.annotation.RequestMethod.PATCH})",
" A patch();",
"",
" @RequestMapping(method = HEAD)",

View File

@@ -1,104 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StringCaseLocaleUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StringCaseLocaleUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(StringCaseLocaleUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.util.Locale.ROOT;",
"",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.ROOT);",
" \"a\".toUpperCase(Locale.ROOT);",
" \"b\".toLowerCase(ROOT);",
" \"b\".toUpperCase(ROOT);",
" \"c\".toLowerCase(Locale.getDefault());",
" \"c\".toUpperCase(Locale.getDefault());",
" \"d\".toLowerCase(Locale.ENGLISH);",
" \"d\".toUpperCase(Locale.ENGLISH);",
" \"e\".toLowerCase(new Locale(\"foo\"));",
" \"e\".toUpperCase(new Locale(\"foo\"));",
"",
" // BUG: Diagnostic contains:",
" \"f\".toLowerCase();",
" // BUG: Diagnostic contains:",
" \"g\".toUpperCase();",
"",
" String h = \"h\";",
" // BUG: Diagnostic contains:",
" h.toLowerCase();",
" String i = \"i\";",
" // BUG: Diagnostic contains:",
" i.toUpperCase();",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase(/* Comment with parens: (). */ );",
" \"b\".toUpperCase();",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.ROOT /* Comment with parens: (). */);",
" \"b\".toUpperCase(Locale.ROOT);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase();",
" \"b\".toUpperCase(/* Comment with parens: (). */ );",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.getDefault());",
" \"b\".toUpperCase(Locale.getDefault() /* Comment with parens: (). */);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,110 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodTree;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
final class MoreASTHelpersTest {
@Test
void findMethods() {
CompilationTestHelper.newInstance(FindMethodsTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void foo() {}",
"",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void bar(int i) {}",
"",
" static class B {",
" // BUG: Diagnostic contains: {foo=0, bar=1, baz=1}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=0, bar=1, baz=1}",
" void baz() {}",
" }",
"}")
.doTest();
}
@Test
void methodExistsInEnclosingClass() {
CompilationTestHelper.newInstance(MethodExistsTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void foo() {}",
"",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void bar(int i) {}",
"",
" static class B {",
" // BUG: Diagnostic contains: {foo=false, bar=true, baz=true}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=false, bar=true, baz=true}",
" void baz() {}",
" }",
"}")
.doTest();
}
private static String createDiagnosticsMessage(
BiFunction<String, VisitorState, Object> valueFunction, VisitorState state) {
return Maps.toMap(ImmutableSet.of("foo", "bar", "baz"), key -> valueFunction.apply(key, state))
.toString();
}
/**
* A {@link BugChecker} that delegates to {@link MoreASTHelpers#findMethods(CharSequence,
* VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class FindMethodsTestChecker extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
createDiagnosticsMessage(
(methodName, s) -> MoreASTHelpers.findMethods(methodName, s).size(), state))
.build();
}
}
/**
* A {@link BugChecker} that delegates to {@link
* MoreASTHelpers#methodExistsInEnclosingClass(CharSequence, VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class MethodExistsTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(createDiagnosticsMessage(MoreASTHelpers::methodExistsInEnclosingClass, state))
.build();
}
}
}

View File

@@ -1,173 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.HAS_METHOD_SOURCE;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import java.util.Map;
import org.junit.jupiter.api.Test;
final class MoreJUnitMatchersTest {
@Test
void methodMatchers() {
CompilationTestHelper.newInstance(MethodMatchersTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import java.util.stream.Stream;",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" @BeforeAll",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" public void beforeAll() {}",
"",
" @BeforeEach",
" @Test",
" // BUG: Diagnostic contains: TEST_METHOD, SETUP_OR_TEARDOWN_METHOD",
" protected void beforeEachAndTest() {}",
"",
" @AfterEach",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" private void afterEach() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" private void afterAll() {}",
"",
" @Test",
" // BUG: Diagnostic contains: TEST_METHOD",
" void test() {}",
"",
" private static Stream<Arguments> booleanArgs() {",
" return Stream.of(arguments(false), arguments(true));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"booleanArgs\")",
" // BUG: Diagnostic contains: TEST_METHOD, HAS_METHOD_SOURCE",
" void parameterizedTest(boolean b) {}",
"",
" @RepeatedTest(2)",
" // BUG: Diagnostic contains: TEST_METHOD",
" private void repeatedTest() {}",
"",
" private void unannotatedMethod() {}",
"}")
.doTest();
}
@Test
void getMethodSourceFactoryNames() {
CompilationTestHelper.newInstance(MethodSourceFactoryNamesTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" @MethodSource",
" // BUG: Diagnostic contains: [matchingMethodSource]",
" void matchingMethodSource(boolean b) {}",
"",
" @MethodSource()",
" // BUG: Diagnostic contains: [matchingMethodSourceWithParens]",
" void matchingMethodSourceWithParens(boolean b) {}",
"",
" @MethodSource(\"\")",
" // BUG: Diagnostic contains: [matchingMethodSourceMadeExplicit]",
" void matchingMethodSourceMadeExplicit(boolean b) {}",
"",
" @MethodSource({\"\"})",
" // BUG: Diagnostic contains: [matchingMethodSourceMadeExplicitWithParens]",
" void matchingMethodSourceMadeExplicitWithParens(boolean b) {}",
"",
" @MethodSource({})",
" // BUG: Diagnostic contains: []",
" void noMethodSources(boolean b) {}",
"",
" @MethodSource(\"myValueFactory\")",
" // BUG: Diagnostic contains: [myValueFactory]",
" void singleCustomMethodSource(boolean b) {}",
"",
" @MethodSource({\"firstValueFactory\", \"secondValueFactory\"})",
" // BUG: Diagnostic contains: [firstValueFactory, secondValueFactory]",
" void twoCustomMethodSources(boolean b) {}",
"",
" @MethodSource({\"myValueFactory\", \"\"})",
" // BUG: Diagnostic contains: [myValueFactory, customAndMatchingMethodSources]",
" void customAndMatchingMethodSources(boolean b) {}",
"}")
.doTest();
}
/**
* A {@link BugChecker} that flags methods matched by {@link Matcher}s of {@link MethodTree}s
* exposed by {@link MoreJUnitMatchers}.
*/
@BugPattern(summary = "Interacts with `MoreJUnitMatchers` for testing purposes", severity = ERROR)
public static final class MethodMatchersTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableMap<String, Matcher<MethodTree>> METHOD_MATCHERS =
ImmutableMap.of(
"TEST_METHOD", TEST_METHOD,
"HAS_METHOD_SOURCE", HAS_METHOD_SOURCE,
"SETUP_OR_TEARDOWN_METHOD", SETUP_OR_TEARDOWN_METHOD);
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
ImmutableSet<String> matches =
METHOD_MATCHERS.entrySet().stream()
.filter(e -> e.getValue().matches(tree, state))
.map(Map.Entry::getKey)
.collect(toImmutableSet());
return matches.isEmpty()
? Description.NO_MATCH
: buildDescription(tree).setMessage(String.join(", ", matches)).build();
}
}
/**
* A {@link BugChecker} that flags methods with a JUnit {@code @MethodSource} annotation by
* enumerating the associated value factory method names.
*/
@BugPattern(summary = "Interacts with `MoreJUnitMatchers` for testing purposes", severity = ERROR)
public static final class MethodSourceFactoryNamesTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
AnnotationTree annotation =
Iterables.getOnlyElement(HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes());
return buildDescription(tree)
.setMessage(MoreJUnitMatchers.getMethodSourceFactoryNames(annotation, tree).toString())
.build();
}
}
}

View File

@@ -1,62 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.AnnotationTree;
import org.junit.jupiter.api.Test;
final class MoreMatchersTest {
@Test
void hasMetaAnnotation() {
CompilationTestHelper.newInstance(TestMatcher.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.api.TestTemplate;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" void negative1() {}",
"",
" @Test",
" void negative2() {}",
"",
" @AfterAll",
" void negative3() {}",
"",
" @TestTemplate",
" void negative4() {}",
"",
" // BUG: Diagnostic contains:",
" @ParameterizedTest",
" void positive1() {}",
"",
" // BUG: Diagnostic contains:",
" @RepeatedTest(2)",
" void positive2() {}",
"}")
.doTest();
}
/** A {@link BugChecker} that delegates to {@link MoreMatchers#hasMetaAnnotation(String)} . */
@BugPattern(summary = "Interacts with `MoreMatchers` for testing purposes", severity = ERROR)
public static final class TestMatcher extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<AnnotationTree> DELEGATE =
MoreMatchers.hasMetaAnnotation("org.junit.jupiter.api.TestTemplate");
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return DELEGATE.matches(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
}
}

View File

@@ -25,6 +25,46 @@ import java.util.List;
import org.junit.jupiter.api.Test;
final class MoreTypesTest {
private static final ImmutableSet<Supplier<Type>> TYPES =
ImmutableSet.of(
// Invalid types.
type("java.lang.Nonexistent"),
generic(type("java.util.Integer"), unbound()),
// Valid types.
type("java.lang.String"),
type("java.lang.Number"),
superOf(type("java.lang.Number")),
subOf(type("java.lang.Number")),
type("java.lang.Integer"),
superOf(type("java.lang.Integer")),
subOf(type("java.lang.Integer")),
type("java.util.Optional"),
raw(type("java.util.Optional")),
generic(type("java.util.Optional"), unbound()),
generic(type("java.util.Optional"), type("java.lang.Number")),
type("java.util.Collection"),
raw(type("java.util.Collection")),
generic(type("java.util.Collection"), unbound()),
generic(type("java.util.Collection"), type("java.lang.Number")),
generic(type("java.util.Collection"), superOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), type("java.lang.Integer")),
generic(type("java.util.Collection"), superOf(type("java.lang.Integer"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Integer"))),
type("java.util.List"),
raw(type("java.util.List")),
generic(type("java.util.List"), unbound()),
generic(type("java.util.List"), type("java.lang.Number")),
generic(type("java.util.List"), superOf(type("java.lang.Number"))),
generic(type("java.util.List"), subOf(type("java.lang.Number"))),
generic(type("java.util.List"), type("java.lang.Integer")),
generic(type("java.util.List"), superOf(type("java.lang.Integer"))),
generic(type("java.util.List"), subOf(type("java.lang.Integer"))),
generic(
type("java.util.Map"),
type("java.lang.String"),
subOf(generic(type("java.util.Collection"), superOf(type("java.lang.Short"))))));
@Test
void matcher() {
CompilationTestHelper.newInstance(SubtypeFlagger.class, getClass())
@@ -119,8 +159,8 @@ final class MoreTypesTest {
}
/**
* A {@link BugChecker} that flags method invocations that are a subtype of any type defined by
* {@link #getTestTypes()}.
* A {@link BugChecker} that flags method invocations that are a subtype of any type contained in
* {@link #TYPES}.
*/
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
public static final class SubtypeFlagger extends BugChecker
@@ -133,7 +173,7 @@ final class MoreTypesTest {
List<String> matches = new ArrayList<>();
for (Supplier<Type> type : getTestTypes()) {
for (Supplier<Type> type : TYPES) {
Type testType = type.get(state);
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
matches.add(Signatures.prettyType(testType));
@@ -144,52 +184,5 @@ final class MoreTypesTest {
? Description.NO_MATCH
: buildDescription(tree).setMessage(matches.toString()).build();
}
/**
* Returns the type suppliers under test.
*
* @implNote The return value of this method should not be assigned to a field, as that would
* prevent mutations introduced by Pitest from being killed.
*/
private static ImmutableSet<Supplier<Type>> getTestTypes() {
return ImmutableSet.of(
// Invalid types.
type("java.lang.Nonexistent"),
generic(type("java.util.Integer"), unbound()),
// Valid types.
type("java.lang.String"),
type("java.lang.Number"),
superOf(type("java.lang.Number")),
subOf(type("java.lang.Number")),
type("java.lang.Integer"),
superOf(type("java.lang.Integer")),
subOf(type("java.lang.Integer")),
type("java.util.Optional"),
raw(type("java.util.Optional")),
generic(type("java.util.Optional"), unbound()),
generic(type("java.util.Optional"), type("java.lang.Number")),
type("java.util.Collection"),
raw(type("java.util.Collection")),
generic(type("java.util.Collection"), unbound()),
generic(type("java.util.Collection"), type("java.lang.Number")),
generic(type("java.util.Collection"), superOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), type("java.lang.Integer")),
generic(type("java.util.Collection"), superOf(type("java.lang.Integer"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Integer"))),
type("java.util.List"),
raw(type("java.util.List")),
generic(type("java.util.List"), unbound()),
generic(type("java.util.List"), type("java.lang.Number")),
generic(type("java.util.List"), superOf(type("java.lang.Number"))),
generic(type("java.util.List"), subOf(type("java.lang.Number"))),
generic(type("java.util.List"), type("java.lang.Integer")),
generic(type("java.util.List"), superOf(type("java.lang.Integer"))),
generic(type("java.util.List"), subOf(type("java.lang.Integer"))),
generic(
type("java.util.Map"),
type("java.lang.String"),
subOf(generic(type("java.util.Collection"), superOf(type("java.lang.Short"))))));
}
}
}

View File

@@ -1,199 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import javax.lang.model.element.Name;
import org.junit.jupiter.api.Test;
final class SourceCodeTest {
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass());
@Test
void deleteWithTrailingWhitespaceAnnotations() {
refactoringTestHelper
.addInputLines("AnnotationToBeDeleted.java", "@interface AnnotationToBeDeleted {}")
.expectUnchanged()
.addInputLines(
"AnotherAnnotationToBeDeleted.java", "@interface AnotherAnnotationToBeDeleted {}")
.expectUnchanged()
.addInputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" @AnnotationToBeDeleted",
" void m() {}",
" }",
"",
" class FirstAnnotation {",
" @AnnotationToBeDeleted",
" @Deprecated",
" void m() {}",
" }",
"",
" class MiddleAnnotation {",
" @Deprecated",
" @AnnotationToBeDeleted",
" @SuppressWarnings(\"foo\")",
" void m() {}",
" }",
"",
" class LastAnnotation {",
" @Deprecated",
" @AnnotationToBeDeleted",
" void m() {}",
" }",
"",
" class MultipleAnnotations {",
" @AnnotationToBeDeleted",
" @AnotherAnnotationToBeDeleted",
" @Deprecated",
" void m() {}",
" }",
"}")
.addOutputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" void m() {}",
" }",
"",
" class FirstAnnotation {",
" @Deprecated",
" void m() {}",
" }",
"",
" class MiddleAnnotation {",
" @Deprecated",
" @SuppressWarnings(\"foo\")",
" void m() {}",
" }",
"",
" class LastAnnotation {",
" @Deprecated",
" void m() {}",
" }",
"",
" class MultipleAnnotations {",
" @Deprecated",
" void m() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void deleteWithTrailingWhitespaceMethods() {
refactoringTestHelper
.addInputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {",
" void methodToBeDeleted() {}",
" }",
"",
" class FirstMethod {",
" void methodToBeDeleted() {}",
"",
" void finalMethod() {}",
" }",
"",
" class MiddleMethod {",
" void initialMethod() {}",
"",
" void methodToBeDeleted() {}",
"",
" void finalMethod() {}",
" }",
"",
" class LastMethod {",
" void initialMethod() {}",
"",
" void methodToBeDeleted() {}",
" }",
"",
" class MultipleMethods {",
" void method1ToBeDeleted() {}",
"",
" void method2ToBeDeleted() {}",
"",
" void middleMethod() {}",
"",
" void method3ToBeDeleted() {}",
"",
" void method4ToBeDeleted() {}",
" }",
"}")
.addOutputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {}",
"",
" class FirstMethod {",
" void finalMethod() {}",
" }",
"",
" class MiddleMethod {",
" void initialMethod() {}",
"",
" void finalMethod() {}",
" }",
"",
" class LastMethod {",
" void initialMethod() {}",
" }",
"",
" class MultipleMethods {",
" void middleMethod() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
/**
* A {@link BugChecker} that uses {@link SourceCode#deleteWithTrailingWhitespace(Tree,
* VisitorState)} to suggest the deletion of annotations and methods with a name containing
* {@value DELETION_MARKER}.
*/
@BugPattern(severity = ERROR, summary = "Interacts with `SourceCode` for testing purposes")
public static final class TestChecker extends BugChecker
implements AnnotationTreeMatcher, MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String DELETION_MARKER = "ToBeDeleted";
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return match(
tree,
ASTHelpers.getAnnotationMirror(tree).getAnnotationType().asElement().getSimpleName(),
state);
}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return match(tree, tree.getName(), state);
}
private Description match(Tree tree, Name name, VisitorState state) {
return name.toString().contains(DELETION_MARKER)
? describeMatch(tree, SourceCode.deleteWithTrailingWhitespace(tree, state))
: Description.NO_MATCH;
}
}
}

View File

@@ -52,7 +52,6 @@ final class RefasterRulesTest {
JUnitRules.class,
LongStreamRules.class,
MapEntryRules.class,
MapRules.class,
MockitoRules.class,
MultimapRules.class,
NullRules.class,

View File

@@ -2,143 +2,18 @@ package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.MapAssert;
import org.assertj.core.api.AbstractMapAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
ArrayList.class,
HashMap.class,
HashSet.class,
ImmutableBiMap.class,
ImmutableList.class,
ImmutableMultiset.class,
ImmutableSortedMap.class,
ImmutableSortedMultiset.class,
ImmutableSortedSet.class,
LinkedHashMap.class,
TreeMap.class,
TreeSet.class);
}
void testAbstractMapAssertIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).containsExactlyEntriesOf(ImmutableMap.of());
assertThat(ImmutableMap.of(2, 0)).containsExactlyEntriesOf(ImmutableBiMap.of());
assertThat(ImmutableMap.of(3, 0)).containsExactlyEntriesOf(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(4, 0)).containsExactlyEntriesOf(new HashMap<>());
assertThat(ImmutableMap.of(5, 0)).containsExactlyEntriesOf(new LinkedHashMap<>());
assertThat(ImmutableMap.of(6, 0)).containsExactlyEntriesOf(new TreeMap<>());
assertThat(ImmutableMap.of(7, 0)).hasSameSizeAs(ImmutableMap.of());
assertThat(ImmutableMap.of(8, 0)).hasSameSizeAs(ImmutableBiMap.of());
assertThat(ImmutableMap.of(9, 0)).hasSameSizeAs(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(10, 0)).hasSameSizeAs(new HashMap<>());
assertThat(ImmutableMap.of(11, 0)).hasSameSizeAs(new LinkedHashMap<>());
assertThat(ImmutableMap.of(12, 0)).hasSameSizeAs(new TreeMap<>());
assertThat(ImmutableMap.of(13, 0)).isEqualTo(ImmutableMap.of());
assertThat(ImmutableMap.of(14, 0)).isEqualTo(ImmutableBiMap.of());
assertThat(ImmutableMap.of(15, 0)).isEqualTo(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(16, 0)).isEqualTo(new HashMap<>());
assertThat(ImmutableMap.of(17, 0)).isEqualTo(new LinkedHashMap<>());
assertThat(ImmutableMap.of(18, 0)).isEqualTo(new TreeMap<>());
assertThat(ImmutableMap.of(19, 0)).containsOnlyKeys(ImmutableList.of());
assertThat(ImmutableMap.of(20, 0)).containsOnlyKeys(new ArrayList<>());
assertThat(ImmutableMap.of(21, 0)).containsOnlyKeys(ImmutableSet.of());
assertThat(ImmutableMap.of(22, 0)).containsOnlyKeys(new HashSet<>());
assertThat(ImmutableMap.of(23, 0)).containsOnlyKeys(ImmutableSortedSet.of());
assertThat(ImmutableMap.of(24, 0)).containsOnlyKeys(new TreeSet<>());
assertThat(ImmutableMap.of(25, 0)).containsOnlyKeys(ImmutableMultiset.of());
assertThat(ImmutableMap.of(26, 0)).containsOnlyKeys(ImmutableSortedMultiset.of());
assertThat(ImmutableMap.of(27, 0)).containsExactly();
assertThat(ImmutableMap.of(28, 0)).containsOnly();
assertThat(ImmutableMap.of(29, 0)).containsOnlyKeys();
}
void testAssertThatMapIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).hasSize(0);
assertThat(ImmutableMap.of(2, 0).isEmpty()).isTrue();
assertThat(ImmutableMap.of(3, 0).size()).isEqualTo(0L);
assertThat(ImmutableMap.of(4, 0).size()).isNotPositive();
assertThat(ImmutableMap.of(5, 0).keySet()).isEmpty();
assertThat(ImmutableMap.of(6, 0).values()).isEmpty();
assertThat(ImmutableMap.of(7, 0).entrySet()).isEmpty();
}
ImmutableSet<MapAssert<Integer, Integer>> testAbstractMapAssertIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEqualTo(ImmutableMap.of()),
assertThat(ImmutableMap.of(2, 0)).isNotEqualTo(ImmutableBiMap.of()),
assertThat(ImmutableMap.of(3, 0)).isNotEqualTo(ImmutableSortedMap.of()),
assertThat(ImmutableMap.of(4, 0)).isNotEqualTo(new HashMap<>()),
assertThat(ImmutableMap.of(5, 0)).isNotEqualTo(new LinkedHashMap<>()),
assertThat(ImmutableMap.of(6, 0)).isNotEqualTo(new TreeMap<>()));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0).isEmpty()).isFalse(),
assertThat(ImmutableMap.of(2, 0).size()).isNotEqualTo(0),
assertThat(ImmutableMap.of(3, 0).size()).isPositive(),
assertThat(ImmutableMap.of(4, 0).keySet()).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0).values()).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0).entrySet()).isNotEmpty());
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
AbstractMapAssert<?, ?, Integer, Integer>
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
return assertThat(ImmutableMap.of(1, 2, 3, 4)).isEqualTo(ImmutableMap.of(1, 2, 3, 4));
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
return assertThat(ImmutableMap.of(1, 2))
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapHasSize() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 2).size()).isEqualTo(1),
assertThat(ImmutableMap.of(3, 4).keySet()).hasSize(1),
assertThat(ImmutableMap.of(5, 6).values()).hasSize(1),
assertThat(ImmutableMap.of(7, 8).entrySet()).hasSize(1));
}
MapAssert<Integer, Integer> testAbstractMapAssertHasSameSizeAs() {
return assertThat(ImmutableMap.of(1, 2)).hasSize(ImmutableMap.of(3, 4).size());
}
AbstractAssert<?, ?> testAssertThatMapContainsKey() {
return assertThat(ImmutableMap.of(1, 2).containsKey(3)).isTrue();
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainKey() {
return assertThat(ImmutableMap.of(1, 2).containsKey(3)).isFalse();
}
AbstractAssert<?, ?> testAssertThatMapContainsValue() {
return assertThat(ImmutableMap.of(1, 2).containsValue(3)).isTrue();
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainValue() {
return assertThat(ImmutableMap.of(1, 2).containsValue(3)).isFalse();
}
AbstractObjectAssert<?, ?> testAssertThatMapContainsEntry() {
return assertThat(ImmutableMap.of(1, 2).get(1)).isEqualTo(2);
}
}

View File

@@ -2,143 +2,18 @@ package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.MapAssert;
import org.assertj.core.api.AbstractMapAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
ArrayList.class,
HashMap.class,
HashSet.class,
ImmutableBiMap.class,
ImmutableList.class,
ImmutableMultiset.class,
ImmutableSortedMap.class,
ImmutableSortedMultiset.class,
ImmutableSortedSet.class,
LinkedHashMap.class,
TreeMap.class,
TreeSet.class);
}
void testAbstractMapAssertIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).isEmpty();
assertThat(ImmutableMap.of(2, 0)).isEmpty();
assertThat(ImmutableMap.of(3, 0)).isEmpty();
assertThat(ImmutableMap.of(4, 0)).isEmpty();
assertThat(ImmutableMap.of(5, 0)).isEmpty();
assertThat(ImmutableMap.of(6, 0)).isEmpty();
assertThat(ImmutableMap.of(7, 0)).isEmpty();
assertThat(ImmutableMap.of(8, 0)).isEmpty();
assertThat(ImmutableMap.of(9, 0)).isEmpty();
assertThat(ImmutableMap.of(10, 0)).isEmpty();
assertThat(ImmutableMap.of(11, 0)).isEmpty();
assertThat(ImmutableMap.of(12, 0)).isEmpty();
assertThat(ImmutableMap.of(13, 0)).isEmpty();
assertThat(ImmutableMap.of(14, 0)).isEmpty();
assertThat(ImmutableMap.of(15, 0)).isEmpty();
assertThat(ImmutableMap.of(16, 0)).isEmpty();
assertThat(ImmutableMap.of(17, 0)).isEmpty();
assertThat(ImmutableMap.of(18, 0)).isEmpty();
assertThat(ImmutableMap.of(19, 0)).isEmpty();
assertThat(ImmutableMap.of(20, 0)).isEmpty();
assertThat(ImmutableMap.of(21, 0)).isEmpty();
assertThat(ImmutableMap.of(22, 0)).isEmpty();
assertThat(ImmutableMap.of(23, 0)).isEmpty();
assertThat(ImmutableMap.of(24, 0)).isEmpty();
assertThat(ImmutableMap.of(25, 0)).isEmpty();
assertThat(ImmutableMap.of(26, 0)).isEmpty();
assertThat(ImmutableMap.of(27, 0)).isEmpty();
assertThat(ImmutableMap.of(28, 0)).isEmpty();
assertThat(ImmutableMap.of(29, 0)).isEmpty();
}
void testAssertThatMapIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).isEmpty();
assertThat(ImmutableMap.of(2, 0)).isEmpty();
assertThat(ImmutableMap.of(3, 0)).isEmpty();
assertThat(ImmutableMap.of(4, 0)).isEmpty();
assertThat(ImmutableMap.of(5, 0)).isEmpty();
assertThat(ImmutableMap.of(6, 0)).isEmpty();
assertThat(ImmutableMap.of(7, 0)).isEmpty();
}
ImmutableSet<MapAssert<Integer, Integer>> testAbstractMapAssertIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(2, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(3, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(4, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0)).isNotEmpty());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(2, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(3, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(4, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0)).isNotEmpty());
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
AbstractMapAssert<?, ?, Integer, Integer>
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
return assertThat(ImmutableMap.of(1, 2, 3, 4))
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2, 3, 4));
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
return assertThat(ImmutableMap.of(1, 2)).containsExactlyEntriesOf(ImmutableMap.of(1, 2));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapHasSize() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 2)).hasSize(1),
assertThat(ImmutableMap.of(3, 4)).hasSize(1),
assertThat(ImmutableMap.of(5, 6)).hasSize(1),
assertThat(ImmutableMap.of(7, 8)).hasSize(1));
}
MapAssert<Integer, Integer> testAbstractMapAssertHasSameSizeAs() {
return assertThat(ImmutableMap.of(1, 2)).hasSameSizeAs(ImmutableMap.of(3, 4));
}
AbstractAssert<?, ?> testAssertThatMapContainsKey() {
return assertThat(ImmutableMap.of(1, 2)).containsKey(3);
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainKey() {
return assertThat(ImmutableMap.of(1, 2)).doesNotContainKey(3);
}
AbstractAssert<?, ?> testAssertThatMapContainsValue() {
return assertThat(ImmutableMap.of(1, 2)).containsValue(3);
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainValue() {
return assertThat(ImmutableMap.of(1, 2)).doesNotContainValue(3);
}
AbstractObjectAssert<?, ?> testAssertThatMapContainsEntry() {
return assertThat(ImmutableMap.of(1, 2).get(1)).isEqualTo(2);
}
}

View File

@@ -6,12 +6,16 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
@@ -19,6 +23,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -38,6 +43,14 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
}
}
Map<RoundingMode, String> testCreateEnumMap() {
return new HashMap<>();
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
}
@@ -82,6 +95,14 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
return !ImmutableList.of().iterator().hasNext();
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getKey);
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getValue);
}
ImmutableSet<Stream<String>> testSplitToStream() {
return ImmutableSet.of(
Streams.stream(Splitter.on(':').split("foo")),

View File

@@ -8,13 +8,18 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
@@ -22,6 +27,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -39,6 +45,14 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
checkIndex(1, 2);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new EnumMap<>(RoundingMode.class);
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").get("bar");
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
}
@@ -81,6 +95,14 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
return Iterables.isEmpty(ImmutableList.of());
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).keySet().stream();
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).values().stream();
}
ImmutableSet<Stream<String>> testSplitToStream() {
return ImmutableSet.of(
Splitter.on(':').splitToStream("foo"),

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.valueOf(10), BigDecimal.valueOf(10L), new BigDecimal("10"));
}
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(new BigDecimal(2), new BigDecimal(2L), new BigDecimal(2.0));
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(new BigDecimal(0), new BigDecimal(0L));
}
}

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.TEN, BigDecimal.TEN, BigDecimal.TEN);
}
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(BigDecimal.valueOf(2), BigDecimal.valueOf(2L), BigDecimal.valueOf(2.0));
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(BigDecimal.valueOf(0), BigDecimal.valueOf(0L));
}
}

View File

@@ -186,8 +186,4 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
? Optional.ofNullable(new LinkedList<String>().remove())
: Optional.empty());
}
void testCollectionForEach() {
ImmutableSet.of(1).stream().forEach(String::valueOf);
}
}

View File

@@ -136,8 +136,4 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
Optional.ofNullable(new LinkedList<String>().poll()),
Optional.ofNullable(new LinkedList<String>().poll()));
}
void testCollectionForEach() {
ImmutableSet.of(1).forEach(String::valueOf);
}
}

View File

@@ -4,43 +4,30 @@ import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(
String::compareTo,
Comparator.comparing(identity()),
Comparator.comparing(s -> s),
Collections.<String>reverseOrder(reverseOrder()),
Comparator.<String>reverseOrder().reversed());
}
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Collections.reverseOrder(),
Collections.reverseOrder(String::compareTo),
Collections.<String>reverseOrder(naturalOrder()),
Comparator.<String>naturalOrder().reversed());
Comparator<String> testReverseOrder() {
return Comparator.<String>naturalOrder().reversed();
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -90,24 +77,8 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(s -> s));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of(
Comparator.<String>naturalOrder().compare("foo", "bar"),
Comparator.<String>reverseOrder().compare("baz", "qux"));
}
int testMinOfVarargs() {
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") <= 0 ? "a" : "b",
"a".compareTo("b") > 0 ? "b" : "a",
"a".compareTo("b") < 0 ? "a" : "b",
"a".compareTo("b") >= 0 ? "b" : "a",
Comparators.min("a", "b", naturalOrder()),
Comparators.max("a", "b", reverseOrder()),
Collections.min(Arrays.asList("a", "b")),
Collections.min(ImmutableList.of("a", "b")),
Collections.min(ImmutableSet.of("a", "b")));
@@ -115,27 +86,13 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "b" : "a",
Collections.min(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.min(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
int testMaxOfVarargs() {
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
Collections.min(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.min(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.min(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") >= 0 ? "a" : "b",
"a".compareTo("b") < 0 ? "b" : "a",
"a".compareTo("b") > 0 ? "a" : "b",
"a".compareTo("b") <= 0 ? "b" : "a",
Comparators.max("a", "b", naturalOrder()),
Comparators.min("a", "b", reverseOrder()),
Collections.max(Arrays.asList("a", "b")),
Collections.max(ImmutableList.of("a", "b")),
Collections.max(ImmutableSet.of("a", "b")));
@@ -143,13 +100,9 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "b" : "a",
Collections.max(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.max(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.max(ImmutableSet.of("a", "b"), (a, b) -> 1));
Collections.max(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.max(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.max(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -11,32 +11,21 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(
naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder());
return ImmutableSet.of(naturalOrder(), naturalOrder(), naturalOrder());
}
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Comparator.reverseOrder(),
Comparator.reverseOrder(),
Comparator.reverseOrder(),
Comparator.reverseOrder());
Comparator<String> testReverseOrder() {
return reverseOrder();
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -79,64 +68,28 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(naturalOrder()));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
}
int testMinOfVarargs() {
return Collections.min(Arrays.asList(1, 2), naturalOrder());
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("b", "a"),
Comparators.min("b", "a"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"));
Comparators.min("a", "b"), Comparators.min("a", "b"), Comparators.min("a", "b"));
}
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", (a, b) -> -1),
Comparators.min("a", "b", (a, b) -> 0),
Comparators.min("a", "b", (a, b) -> 1));
}
int testMaxOfVarargs() {
return Collections.max(Arrays.asList(1, 2), naturalOrder());
Comparators.min(new Object(), new Object(), (a, b) -> -1),
Comparators.min(new Object(), new Object(), (a, b) -> 0),
Comparators.min(new Object(), new Object(), (a, b) -> 1));
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("b", "a"),
Comparators.max("b", "a"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"));
Comparators.max("a", "b"), Comparators.max("a", "b"), Comparators.max("a", "b"));
}
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", (a, b) -> -1),
Comparators.max("a", "b", (a, b) -> 0),
Comparators.max("a", "b", (a, b) -> 1));
Comparators.max(new Object(), new Object(), (a, b) -> -1),
Comparators.max(new Object(), new Object(), (a, b) -> 0),
Comparators.max(new Object(), new Object(), (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -1,54 +0,0 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new HashMap<>();
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).keySet().isEmpty(),
ImmutableMap.of("bar", 2).values().isEmpty(),
ImmutableMap.of("baz", 3).entrySet().isEmpty());
}
ImmutableSet<Integer> testMapSize() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).keySet().size(),
ImmutableMap.of("bar", 2).values().size(),
ImmutableMap.of("baz", 3).entrySet().size());
}
boolean testMapContainsKey() {
return ImmutableMap.of("foo", 1).keySet().contains("bar");
}
boolean testMapContainsValue() {
return ImmutableMap.of("foo", 1).values().contains(2);
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getKey);
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getValue);
}
}

View File

@@ -1,55 +0,0 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new EnumMap<>(RoundingMode.class);
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").get("bar");
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).isEmpty(),
ImmutableMap.of("bar", 2).isEmpty(),
ImmutableMap.of("baz", 3).isEmpty());
}
ImmutableSet<Integer> testMapSize() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).size(),
ImmutableMap.of("bar", 2).size(),
ImmutableMap.of("baz", 3).size());
}
boolean testMapContainsKey() {
return ImmutableMap.of("foo", 1).containsKey("bar");
}
boolean testMapContainsValue() {
return ImmutableMap.of("foo", 1).containsValue(2);
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).keySet().stream();
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).values().stream();
}
}

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableList;
@@ -34,32 +33,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromCallable(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
Mono<Integer> testMonoJust() {
return Mono.justOrEmpty(Optional.of(1));
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(Optional.ofNullable(1));
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.fromCallable(() -> Optional.of(1).orElse(null)),
Mono.fromSupplier(() -> Optional.of(2).orElse(null)));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::justOrEmpty);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Optional.of(1).map(Mono::just).orElse(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.just("foo").zipWith(Mono.just(1));
}
@@ -104,16 +83,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1, 2, 3).take(1);
}
Mono<String> testMonoDefaultIfEmpty() {
return Mono.just("foo").switchIfEmpty(Mono.just("bar"));
}
ImmutableSet<Flux<String>> testFluxDefaultIfEmpty() {
return ImmutableSet.of(
Flux.just("foo").switchIfEmpty(Mono.just("bar")),
Flux.just("baz").switchIfEmpty(Flux.just("qux")));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1).switchIfEmpty(Mono.empty());
}
@@ -125,16 +94,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
return ImmutableSet.of(
Flux.just(1).flatMap(Mono::just, 1),
Flux.just(2).flatMapSequential(Mono::just, 1),
Flux.just(3).map(Mono::just).concatMap(identity()));
Flux.just(1).flatMap(Mono::just, 1), Flux.just(2).flatMapSequential(Mono::just, 1));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
return ImmutableSet.of(
Flux.just(1).flatMap(Mono::just, 1, 3),
Flux.just(2).flatMapSequential(Mono::just, 1, 4),
Flux.just(3).map(Mono::just).concatMap(identity(), 5));
Flux.just(1).flatMap(Mono::just, 1, 3), Flux.just(2).flatMapSequential(Mono::just, 1, 4));
}
Flux<Integer> testFluxConcatMapIterable() {
@@ -213,15 +178,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(1).switchMap(n -> Mono.fromSupplier(() -> n * 2)));
}
ImmutableSet<Flux<String>> testMonoFlux() {
return ImmutableSet.of(
Mono.just("foo").flatMapMany(Mono::just),
Mono.just("bar").flatMapMany(Flux::just),
Flux.concat(Mono.just("baz")));
Flux<String> testMonoFlux() {
return Flux.concat(Mono.just("foo"));
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty());
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty()),
Mono.just("bar").map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
}
Mono<Number> testMonoCast() {
@@ -232,14 +196,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).map(Number.class::cast);
}
Mono<String> testMonoFlatMap() {
return Mono.just("foo").map(Mono::just).flatMap(identity());
}
Flux<String> testMonoFlatMapMany() {
return Mono.just("foo").map(Mono::just).flatMapMany(identity());
}
ImmutableSet<Flux<String>> testConcatMapIterableIdentity() {
return ImmutableSet.of(
Flux.just(ImmutableList.of("foo")).concatMap(list -> Flux.fromIterable(list)),

View File

@@ -37,32 +37,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromSupplier(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.empty(), Mono.empty());
}
Mono<Integer> testMonoJust() {
return Mono.just(1);
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(1);
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.defer(() -> Mono.justOrEmpty(Optional.of(1))),
Mono.defer(() -> Mono.justOrEmpty(Optional.of(2))));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::just);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Mono.justOrEmpty(Optional.of(1)).switchIfEmpty(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.zip(Mono.just("foo"), Mono.just(1));
}
@@ -110,15 +90,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1, 2, 3).take(1, true);
}
Mono<String> testMonoDefaultIfEmpty() {
return Mono.just("foo").defaultIfEmpty("bar");
}
ImmutableSet<Flux<String>> testFluxDefaultIfEmpty() {
return ImmutableSet.of(
Flux.just("foo").defaultIfEmpty("bar"), Flux.just("baz").defaultIfEmpty("qux"));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1);
}
@@ -128,17 +99,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
return ImmutableSet.of(
Flux.just(1).concatMap(Mono::just),
Flux.just(2).concatMap(Mono::just),
Flux.just(3).concatMap(Mono::just));
return ImmutableSet.of(Flux.just(1).concatMap(Mono::just), Flux.just(2).concatMap(Mono::just));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
return ImmutableSet.of(
Flux.just(1).concatMap(Mono::just, 3),
Flux.just(2).concatMap(Mono::just, 4),
Flux.just(3).concatMap(Mono::just, 5));
Flux.just(1).concatMap(Mono::just, 3), Flux.just(2).concatMap(Mono::just, 4));
}
Flux<Integer> testFluxConcatMapIterable() {
@@ -214,13 +180,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(1).mapNotNull(n -> n * 2));
}
ImmutableSet<Flux<String>> testMonoFlux() {
return ImmutableSet.of(
Mono.just("foo").flux(), Mono.just("bar").flux(), Mono.just("baz").flux());
Flux<String> testMonoFlux() {
return Mono.just("foo").flux();
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").flux().collect(toOptional());
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").flux().collect(toOptional()),
Mono.just("bar").flux().collect(toOptional()));
}
Mono<Number> testMonoCast() {
@@ -231,14 +198,6 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).cast(Number.class);
}
Mono<String> testMonoFlatMap() {
return Mono.just("foo").flatMap(Mono::just);
}
Flux<String> testMonoFlatMapMany() {
return Mono.just("foo").flatMapMany(Mono::just);
}
ImmutableSet<Flux<String>> testConcatMapIterableIdentity() {
return ImmutableSet.of(
Flux.just(ImmutableList.of("foo")).concatMapIterable(identity()),

117
pom.xml
View File

@@ -146,7 +146,7 @@
one place. We use these to keep dependencies in sync. Version numbers
that need to be referenced only once should *not* be listed here. -->
<version.auto-service>1.0.1</version.auto-service>
<version.auto-value>1.10.1</version.auto-value>
<version.auto-value>1.10</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.16</version.error-prone-orig>
@@ -154,10 +154,18 @@
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.8.6</version.maven>
<version.mockito>4.9.0</version.mockito>
<version.mockito>4.8.1</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.10.5</version.nullaway>
<version.pitest-git>1.0.3</version.pitest-git>
<version.nullaway>0.10.4</version.nullaway>
<!-- XXX: Two other dependencies are potentially of interest:
`com.palantir.assertj-automation:assertj-refaster-rules` and
`com.palantir.baseline:baseline-refaster-rules` contain Refaster rules
that aren't currently applied. We should use
`RefasterRuleBuilderScanner` to convert those to `.refaster` files so
that we can pick them up. (But in case of `baseline-refaster-rules`
perhaps we can simply incorporate all of them.) -->
<version.palantir-assertj-automation>0.6.0</version.palantir-assertj-automation>
<version.palantir-baseline>4.145.0</version.palantir-baseline>
<version.surefire>2.22.2</version.surefire>
</properties>
@@ -211,7 +219,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.14.1</version>
<version>2.13.4.20221013</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -269,7 +277,21 @@
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<version>7.11.1</version>
<version>7.11.0</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.palantir.assertj-automation</groupId>
<artifactId>assertj-error-prone</artifactId>
<version>${version.palantir-assertj-automation}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.palantir.baseline</groupId>
<artifactId>baseline-error-prone</artifactId>
<version>${version.palantir-baseline}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
@@ -281,7 +303,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2022.0.0</version>
<version>2020.0.24</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -293,12 +315,12 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.9</version>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.7</version>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -343,7 +365,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.28.0</version>
<version>3.27.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -377,19 +399,19 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.5</version>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.24</version>
<version>5.3.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.7.6</version>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -429,16 +451,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-git-maven-plugin</artifactId>
<version>${version.pitest-git}</version>
</plugin>
<plugin>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-github-maven-plugin</artifactId>
<version>${version.pitest-git}</version>
</plugin>
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
@@ -771,7 +783,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.5.0</version>
<version>10.4</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -832,6 +844,20 @@
<artifactId>nopen-checker</artifactId>
<version>${version.nopen-checker}</version>
</path>
<!-- XXX: Before enabling these plugins we'll
need to resolve some violations. Some of the
checks will need to be disabled.
<path>
<groupId>com.palantir.assertj-automation</groupId>
<artifactId>assertj-error-prone</artifactId>
<version>${version.palantir-assertj-automation}</version>
</path>
<path>
<groupId>com.palantir.baseline</groupId>
<artifactId>baseline-error-prone</artifactId>
<version>${version.palantir-baseline}</version>
</path>
-->
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
@@ -870,16 +896,16 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.4.0</version>
<version>3.3.0</version>
<configuration>
<!-- XXX: Drop `ignoreAllNonTestScoped` once
https://issues.apache.org/jira/browse/MNG-6058 is
resolved. (See
https://issues.apache.org/jira/browse/MDEP-791 for
context.) -->
<ignoreAllNonTestScoped>true</ignoreAllNonTestScoped>
<ignoreDirect>false</ignoreDirect>
<ignoreNonCompile>true</ignoreNonCompile>
<!-- XXX: Drop/review this configuration once
https://issues.apache.org/jira/browse/MDEP-791 is
resolved. -->
<ignoredNonTestScopedDependencies>
<ignoredNonTestScopedDependency>*</ignoredNonTestScopedDependency>
</ignoredNonTestScopedDependencies>
</configuration>
</plugin>
<plugin>
@@ -958,7 +984,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.0</version>
<version>3.0.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -1203,7 +1229,7 @@
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.5.0</version>
<version>2.4.0</version>
<configuration>
<exclusionPatterns>
<!-- The plugin suggests replacing usages of
@@ -1239,19 +1265,11 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.10.3</version>
<version>1.9.9</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
<excludedClass>*.AutoValue_*</excludedClass>
<!-- Refaster rule collections. -->
<excludedClass>*.refaster*.*Rules*</excludedClass>
</excludedClasses>
<failWhenNoMutations>false</failWhenNoMutations>
<mutators>
<mutator>EXTENDED</mutator>
<mutator>STRONGER</mutator>
</mutators>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
contention. -->
@@ -1260,21 +1278,6 @@
<timestampedReports>false</timestampedReports>
</configuration>
<dependencies>
<dependency>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-git-plugin</artifactId>
<version>${version.pitest-git}</version>
</dependency>
<dependency>
<groupId>com.groupcdg.arcmutate</groupId>
<artifactId>base</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.groupcdg.pitest</groupId>
<artifactId>pitest-accelerator-junit5</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>