mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
79 Commits
gdejong/ps
...
rossendrij
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf38b37c8c | ||
|
|
b1c815770b | ||
|
|
bc1f204877 | ||
|
|
cf995ece2b | ||
|
|
d427e298e2 | ||
|
|
ae327d8d64 | ||
|
|
a6f794de3d | ||
|
|
1794d36053 | ||
|
|
ee62af4a86 | ||
|
|
1afce12b52 | ||
|
|
f585306a1f | ||
|
|
4f9aba83ec | ||
|
|
066591c379 | ||
|
|
789f8c86f2 | ||
|
|
0ccebcc9c4 | ||
|
|
8803d23a8e | ||
|
|
5afa7e1878 | ||
|
|
8e3beb9d5c | ||
|
|
96ab66cdcf | ||
|
|
330328329f | ||
|
|
f46859ae3a | ||
|
|
6d15cfe7ff | ||
|
|
fa1bb8aa94 | ||
|
|
415ae35906 | ||
|
|
7cc8abc3de | ||
|
|
16c8bb0b27 | ||
|
|
609d80c9fa | ||
|
|
793bda50d2 | ||
|
|
5d86a66791 | ||
|
|
84d425e4ca | ||
|
|
4fead24e73 | ||
|
|
92df829801 | ||
|
|
98185b92ae | ||
|
|
1b6356a876 | ||
|
|
919a7c7ebe | ||
|
|
79b0123f41 | ||
|
|
3967542edf | ||
|
|
0f05d15dd2 | ||
|
|
5bdb90634a | ||
|
|
1808164c0d | ||
|
|
f451b4cb73 | ||
|
|
d4c95299eb | ||
|
|
1b93843d62 | ||
|
|
3929b39e62 | ||
|
|
81f701fd9e | ||
|
|
b040648400 | ||
|
|
79ca98d20f | ||
|
|
a6122270a9 | ||
|
|
4c0f2c966c | ||
|
|
778a6e7043 | ||
|
|
b6146efaf6 | ||
|
|
fcaa1f7068 | ||
|
|
ece33b0061 | ||
|
|
48772a044e | ||
|
|
281534aeca | ||
|
|
42e632e5db | ||
|
|
7febccb7ff | ||
|
|
d5c1c858d5 | ||
|
|
55d2622380 | ||
|
|
f10f2c9209 | ||
|
|
99f85614fd | ||
|
|
be24edadae | ||
|
|
068c03708b | ||
|
|
9c330981ea | ||
|
|
b780c05dc0 | ||
|
|
16955a9cfa | ||
|
|
8fa3ff3702 | ||
|
|
022a3d293e | ||
|
|
81227cdd94 | ||
|
|
04d886c031 | ||
|
|
afb2a28dcf | ||
|
|
dc0f90e981 | ||
|
|
2196bbd8f9 | ||
|
|
f3b81304b9 | ||
|
|
b0d374040a | ||
|
|
45dfc53d40 | ||
|
|
6cb10ffe2f | ||
|
|
92f2b0ab0f | ||
|
|
21388273c5 |
25
.github/workflows/build.yaml
vendored
25
.github/workflows/build.yaml
vendored
@@ -7,10 +7,27 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
jdk: [ 11.0.16, 17.0.4 ]
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 11.0.16, 17.0.4, 19 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-12
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: ubuntu-22.04
|
||||
jdk: 20-ea
|
||||
distribution: zulu
|
||||
experimental: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
# We run the build twice for each supported JDK: once against the
|
||||
# original Error Prone release, using only Error Prone checks available
|
||||
@@ -20,10 +37,10 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.6.0
|
||||
uses: actions/setup-java@v3.8.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
distribution: temurin
|
||||
distribution: ${{ matrix.distribution }}
|
||||
cache: maven
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
|
||||
20
.github/workflows/deploy-website.yaml
vendored
20
.github/workflows/deploy-website.yaml
vendored
@@ -3,25 +3,22 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master, website ]
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
pages: write
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: ruby/setup-ruby@v1.118.0
|
||||
- uses: ruby/setup-ruby@v1.126.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@v2.1.2
|
||||
uses: actions/configure-pages@v2.1.3
|
||||
- name: Generate documentation
|
||||
run: ./generate-docs.sh
|
||||
- name: Build website with Jekyll
|
||||
@@ -33,12 +30,15 @@ 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.4
|
||||
uses: actions/upload-pages-artifact@v1.0.5
|
||||
with:
|
||||
path: ./website/_site
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/website'
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment:
|
||||
name: github-pages
|
||||
@@ -46,4 +46,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1.2.2
|
||||
uses: actions/deploy-pages@v1.2.3
|
||||
|
||||
37
.github/workflows/pitest-analyze-pr.yml
vendored
Normal file
37
.github/workflows/pitest-analyze-pr.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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
|
||||
35
.github/workflows/pitest-update-pr.yml
vendored
Normal file
35
.github/workflows/pitest-update-pr.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# 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
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
{
|
||||
"matchPackagePatterns": [
|
||||
"^com\\.palantir\\.baseline:baseline-error-prone$"
|
||||
"^ruby\\/setup-ruby$"
|
||||
],
|
||||
"schedule": "* * 1 * *"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ loves Error Prone: producing high-quality and consistent Java
|
||||
code_][picnic-blog-ep-post].
|
||||
|
||||
[![Maven Central][maven-central-badge]][maven-central-search]
|
||||
[![Reproducible Builds][reproducible-builds-badge]][reproducible-builds-report]
|
||||
[![GitHub Actions][github-actions-build-badge]][github-actions-build-master]
|
||||
[![License][license-badge]][license]
|
||||
[![PRs Welcome][pr-badge]][contributing]
|
||||
@@ -165,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 [PIT][pitest]. The
|
||||
- `./run-mutation-tests.sh` runs mutation tests using [Pitest][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].
|
||||
@@ -226,3 +227,5 @@ guidelines][contributing].
|
||||
[refaster]: https://errorprone.info/docs/refaster
|
||||
[refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java
|
||||
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
|
||||
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
|
||||
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
|
||||
|
||||
7
cdg-pitest-licence.txt
Normal file
7
cdg-pitest-licence.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -93,6 +93,11 @@
|
||||
<artifactId>reactor-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
@@ -168,6 +173,11 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
|
||||
@@ -15,7 +15,6 @@ 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;
|
||||
@@ -24,6 +23,7 @@ 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, SuggestedFix.delete(annotation));
|
||||
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.stream.Collector;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
|
||||
@@ -50,7 +51,8 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!COLLECTOR_METHOD.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)
|
||||
|| !COLLECTOR_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ 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;
|
||||
@@ -22,6 +21,7 @@ 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, SuggestedFix.delete(tree));
|
||||
return describeMatch(tree, SourceCode.deleteWithTrailingWhitespace(tree, state));
|
||||
}
|
||||
|
||||
private static boolean isInPossibleTestHelperClass(VisitorState state) {
|
||||
|
||||
@@ -5,6 +5,10 @@ import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -17,11 +21,14 @@ import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@@ -33,11 +40,12 @@ import reactor.core.publisher.Flux;
|
||||
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
|
||||
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
|
||||
* avoids potentially saturating the thread pool on which subscription happens. If {@code
|
||||
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
|
||||
* {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
* concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code
|
||||
* flatMap} or {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
*
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
|
||||
* by this check because there is no clear alternative to point to.
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function,
|
||||
* java.util.function.Supplier)} is not flagged by this check because there is no clear alternative
|
||||
* to point to.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
@@ -52,11 +60,16 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
|
||||
private static final Supplier<Type> FLUX =
|
||||
Suppliers.typeFromString("reactor.core.publisher.Flux");
|
||||
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.onDescendantOf(FLUX)
|
||||
.namedAnyOf("flatMap", "flatMapSequential")
|
||||
.withParameters(Function.class.getName());
|
||||
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
|
||||
VisitorState.memoize(
|
||||
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
|
||||
/** Instantiates a new {@link FluxFlatMapUsage} instance. */
|
||||
public FluxFlatMapUsage() {}
|
||||
@@ -67,14 +80,27 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
|
||||
.addFix(
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build())
|
||||
.build();
|
||||
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
|
||||
SuggestedFix concurrencyCapFix =
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build();
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
|
||||
if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) {
|
||||
/*
|
||||
* Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g.
|
||||
* if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit
|
||||
* concurrently bound, in favour of sequential subscriptions using `Flux#concatMap`.
|
||||
*/
|
||||
description.addFix(concurrencyCapFix).addFix(serializationFix);
|
||||
} else {
|
||||
description.addFix(serializationFix).addFix(concurrencyCapFix);
|
||||
}
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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.SIMPLIFICATION;
|
||||
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.LambdaExpressionTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.InstanceOfTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
|
||||
* of the form {@code T.class::isInstance}.
|
||||
*
|
||||
* @see MethodReferenceUsage
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
|
||||
link = BUG_PATTERNS_BASE_URL + "IsInstanceLambdaUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link IsInstanceLambdaUsage} instance. */
|
||||
public IsInstanceLambdaUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
if (tree.getKind() != Kind.LAMBDA_EXPRESSION || tree.getBody().getKind() != Kind.INSTANCE_OF) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(
|
||||
tree,
|
||||
SourceCode.treeToString(((InstanceOfTree) tree.getBody()).getType(), state)
|
||||
+ ".class::isInstance"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ 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;
|
||||
@@ -25,18 +27,13 @@ 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 javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
|
||||
@@ -64,20 +61,6 @@ 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() {}
|
||||
@@ -142,7 +125,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
|
||||
* </ul>
|
||||
*/
|
||||
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
|
||||
if (isMethodInEnclosingClass(methodName, state)) {
|
||||
if (MoreASTHelpers.methodExistsInEnclosingClass(methodName, state)) {
|
||||
return Optional.of(
|
||||
String.format("a method named `%s` already exists in this class", methodName));
|
||||
}
|
||||
@@ -158,15 +141,6 @@ 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)
|
||||
@@ -188,18 +162,4 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
|
||||
*
|
||||
* @see IsInstanceLambdaUsage
|
||||
*/
|
||||
// XXX: Other custom expressions we could rewrite:
|
||||
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
|
||||
@@ -50,6 +52,9 @@ import javax.lang.model.element.Name;
|
||||
// `not(some::reference)`.
|
||||
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
|
||||
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
|
||||
// XXX: Expressions of the form `i -> SomeType.class.isInstance(i)` are not replaced; fix that using
|
||||
// a suitable generalization.
|
||||
// XXX: Consider folding the `IsInstanceLambdaUsage` check into this class.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer method references over lambda expressions",
|
||||
|
||||
@@ -4,9 +4,11 @@ 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 tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -16,9 +18,7 @@ import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A {@link BugChecker} that flags nesting of {@link Optional Optionals}. */
|
||||
@@ -33,24 +33,16 @@ import java.util.Optional;
|
||||
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
|
||||
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
|
||||
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
|
||||
|
||||
/** Instantiates a new {@link NestedOptionals} instance. */
|
||||
public NestedOptionals() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
|
||||
Type optionalType = OPTIONAL.get(state);
|
||||
Type type = ASTHelpers.getType(tree);
|
||||
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Type> typeArguments = type.getTypeArguments();
|
||||
return !typeArguments.isEmpty()
|
||||
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
|
||||
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.Set;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which suggests a canonical set of modifiers for Refaster class and method
|
||||
* A {@link BugChecker} that suggests a canonical set of modifiers for Refaster class and method
|
||||
* definitions.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
|
||||
@@ -26,6 +26,7 @@ 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 tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags methods with Spring's {@code @Scheduled} annotation that lack New
|
||||
@@ -51,7 +52,8 @@ public final class ScheduledTransactionTrace extends BugChecker implements Metho
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!IS_SCHEDULED.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|
||||
|| !IS_SCHEDULED.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"org.springframework.http.HttpMethod",
|
||||
"org.springframework.http.MediaType",
|
||||
"org.testng.Assert",
|
||||
"reactor.function.TupleUtils");
|
||||
"reactor.function.TupleUtils",
|
||||
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
|
||||
|
||||
/** Type members that should be statically imported. */
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -30,8 +31,8 @@ import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@link String#format(String, Object...)} invocations which can
|
||||
* be replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
|
||||
* A {@link BugChecker} that flags {@link String#format(String, Object...)} invocations which can be
|
||||
* replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
|
||||
* String#valueOf} invocation.
|
||||
*/
|
||||
// XXX: What about `v1 + "sep" + v2` and similar expressions? Do we want to rewrite those to
|
||||
@@ -40,7 +41,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `String#join` over `String#format`",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "StringJoin",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class StringJoin extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
|
||||
@@ -26,6 +26,9 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/** A {@link BugChecker} that flags illegal time-zone related operations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -58,7 +61,10 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
.onClassAny(
|
||||
LocalDate.class.getName(),
|
||||
LocalDateTime.class.getName(),
|
||||
LocalTime.class.getName())
|
||||
LocalTime.class.getName(),
|
||||
OffsetDateTime.class.getName(),
|
||||
OffsetTime.class.getName(),
|
||||
ZonedDateTime.class.getName())
|
||||
.named("now"),
|
||||
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.tools.javac.code.BoundKind;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A set of helper methods which together define a DSL for defining {@link Type types}.
|
||||
*
|
||||
* <p>These methods are meant to be statically imported. Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Supplier<Type> type =
|
||||
* VisitorState.memoize(
|
||||
* generic(
|
||||
* type("reactor.core.publisher.Flux"),
|
||||
* subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
* }</pre>
|
||||
*
|
||||
* This statement produces a memoized supplier of the type {@code Flux<? extends Publisher<?>>}.
|
||||
*/
|
||||
public final class MoreTypes {
|
||||
private MoreTypes() {}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the type with the given fully qualified name.
|
||||
*
|
||||
* <p>This method should only be used when building more complex types in combination with other
|
||||
* {@link MoreTypes} methods. In other cases prefer directly calling {@link
|
||||
* Suppliers#typeFromString(String)}.
|
||||
*
|
||||
* @param typeName The type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> type(String typeName) {
|
||||
return Suppliers.typeFromString(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the described generic type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @param typeArgs The desired type arguments.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
// XXX: The given `type` should be a generic type, so perhaps `withParams` would be a better
|
||||
// method name. But the DSL wouldn't look as nice that way.
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) -> {
|
||||
List<Type> params =
|
||||
Arrays.stream(typeArgs).map(s -> s.get(state)).collect(toCollection(ArrayList::new));
|
||||
if (params.stream().anyMatch(Objects::isNull)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.getType(baseType, /* isArray= */ false, params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a raw (erased, non-generic) variant of the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> raw(Supplier<Type> type) {
|
||||
return propagateNull(type, (state, baseType) -> baseType.tsym.erasure(state.getTypes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? super T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> superOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(baseType, BoundKind.SUPER, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? extends T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> subOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(
|
||||
type.get(state), BoundKind.EXTENDS, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unbound wildcard type ({@code ?}).
|
||||
*
|
||||
* @return A supplier which returns the described type.
|
||||
*/
|
||||
public static Supplier<Type> unbound() {
|
||||
return state ->
|
||||
new Type.WildcardType(
|
||||
state.getSymtab().objectType, BoundKind.UNBOUND, state.getSymtab().boundClass);
|
||||
}
|
||||
|
||||
private static Supplier<Type> propagateNull(
|
||||
Supplier<Type> type, BiFunction<VisitorState, Type, Type> transformer) {
|
||||
return state ->
|
||||
Optional.ofNullable(type.get(state)).map(t -> transformer.apply(state, t)).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,21 @@
|
||||
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() {}
|
||||
|
||||
/**
|
||||
@@ -24,4 +31,32 @@ 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,
|
||||
"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.sun.tools.javac.code.ClassFinder;
|
||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
|
||||
/**
|
||||
* Utility class that helps decide whether it is appropriate to introduce references to (well-known)
|
||||
* third-party libraries.
|
||||
*
|
||||
* <p>This class should be used by {@link BugChecker}s that may otherwise suggest the introduction
|
||||
* of code that depends on possibly-not-present third-party libraries.
|
||||
*/
|
||||
// XXX: Consider giving users more fine-grained control. This would be beneficial in cases where a
|
||||
// dependency is on the classpath, but new usages are undesirable.
|
||||
public enum ThirdPartyLibrary {
|
||||
/**
|
||||
* AssertJ.
|
||||
*
|
||||
* @see <a href="https://assertj.github.io/doc">AssertJ documentation</a>
|
||||
*/
|
||||
ASSERTJ("org.assertj.core.api.Assertions"),
|
||||
/**
|
||||
* Google's Guava.
|
||||
*
|
||||
* @see <a href="https://github.com/google/guava">Guava on GitHub</a>
|
||||
*/
|
||||
GUAVA("com.google.common.collect.ImmutableList"),
|
||||
/**
|
||||
* New Relic's Java agent API.
|
||||
*
|
||||
* @see <a href="https://github.com/newrelic/newrelic-java-agent/tree/main/newrelic-api">New Relic
|
||||
* Java agent API on GitHub</a>
|
||||
*/
|
||||
NEW_RELIC_AGENT_API("com.newrelic.api.agent.Agent"),
|
||||
/**
|
||||
* VMWare's Project Reactor.
|
||||
*
|
||||
* @see <a href="https://projectreactor.io">Home page</a>
|
||||
*/
|
||||
REACTOR("reactor.core.publisher.Flux");
|
||||
|
||||
private static final String IGNORE_CLASSPATH_COMPAT_FLAG =
|
||||
"ErrorProneSupport:IgnoreClasspathCompat";
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker" /* Supplier is deterministic. */)
|
||||
private final Supplier<Boolean> canUse;
|
||||
|
||||
/**
|
||||
* Instantiates a {@link ThirdPartyLibrary} enum value.
|
||||
*
|
||||
* @param witnessFqcn The fully-qualified class name of a type that is expected to be on the
|
||||
* classpath iff the associated third-party library is on the classpath.
|
||||
*/
|
||||
ThirdPartyLibrary(String witnessFqcn) {
|
||||
this.canUse = VisitorState.memoize(state -> canIntroduceUsage(witnessFqcn, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether it is okay to introduce a dependency on this well-known third party library in
|
||||
* the given context.
|
||||
*
|
||||
* @param state The context under consideration.
|
||||
* @return {@code true} iff it is okay to assume or create a dependency on this library.
|
||||
*/
|
||||
public boolean isIntroductionAllowed(VisitorState state) {
|
||||
return canUse.get(state);
|
||||
}
|
||||
|
||||
private static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to determine whether a class with the given FQCN is on the classpath.
|
||||
*
|
||||
* <p>The {@link VisitorState}'s symbol table is consulted first. If the type has not yet been
|
||||
* loaded, then an attempt is made to do so.
|
||||
*/
|
||||
private static boolean isKnownClass(String className, VisitorState state) {
|
||||
return state.getTypeFromString(className) != null || canLoadClass(className, state);
|
||||
}
|
||||
|
||||
private static boolean canLoadClass(String className, VisitorState state) {
|
||||
ClassFinder classFinder = ClassFinder.instance(state.context);
|
||||
Name binaryName = state.binaryNameFromClassname(className);
|
||||
try {
|
||||
classFinder.loadClass(state.getSymtab().unnamedModule, binaryName);
|
||||
return true;
|
||||
} catch (CompletionFailure e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreClasspath(VisitorState state) {
|
||||
return state
|
||||
.errorProneOptions()
|
||||
.getFlags()
|
||||
.getBoolean(IGNORE_CLASSPATH_COMPAT_FLAG)
|
||||
.orElse(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,148 @@
|
||||
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) {
|
||||
@@ -34,4 +166,83 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,9 @@ 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;
|
||||
@@ -22,9 +19,7 @@ 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;
|
||||
@@ -32,19 +27,16 @@ 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;
|
||||
@@ -566,173 +558,8 @@ final class AssertJRules {
|
||||
// Map
|
||||
//
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 AssertThatMapContainsEntry<K, V> {
|
||||
@BeforeTemplate
|
||||
ObjectAssert<?> before(Map<K, V> map, K key, V value) {
|
||||
|
||||
@@ -19,11 +19,8 @@ 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;
|
||||
@@ -73,32 +70,6 @@ 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.
|
||||
@@ -224,32 +195,6 @@ 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
|
||||
|
||||
@@ -50,17 +50,18 @@ final class BigDecimalRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
/** 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 {
|
||||
@BeforeTemplate
|
||||
BigDecimal before(long value) {
|
||||
BigDecimal before(double value) {
|
||||
return new BigDecimal(value);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
BigDecimal after(long value) {
|
||||
BigDecimal after(double value) {
|
||||
return BigDecimal.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -404,6 +405,19 @@ 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?
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
@@ -24,6 +25,7 @@ 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. */
|
||||
@@ -37,7 +39,10 @@ final class ComparatorRules {
|
||||
@BeforeTemplate
|
||||
Comparator<T> before() {
|
||||
return Refaster.anyOf(
|
||||
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
|
||||
T::compareTo,
|
||||
comparing(Refaster.anyOf(identity(), v -> v)),
|
||||
Collections.<T>reverseOrder(reverseOrder()),
|
||||
Comparator.<T>reverseOrder().reversed());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -51,11 +56,16 @@ final class ComparatorRules {
|
||||
static final class ReverseOrder<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
Comparator<T> before() {
|
||||
return Comparator.<T>naturalOrder().reversed();
|
||||
return Refaster.anyOf(
|
||||
Collections.reverseOrder(),
|
||||
Collections.reverseOrder(T::compareTo),
|
||||
Collections.<T>reverseOrder(naturalOrder()),
|
||||
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();
|
||||
}
|
||||
@@ -189,15 +199,54 @@ 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 Collections.min(
|
||||
Refaster.anyOf(
|
||||
Arrays.asList(value1, value2),
|
||||
ImmutableList.of(value1, value2),
|
||||
ImmutableSet.of(value1, 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))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -212,12 +261,17 @@ final class ComparatorRules {
|
||||
static final class MinOfPairCustomOrder<T> {
|
||||
@BeforeTemplate
|
||||
T before(T value1, T value2, Comparator<T> cmp) {
|
||||
return Collections.min(
|
||||
Refaster.anyOf(
|
||||
Arrays.asList(value1, value2),
|
||||
ImmutableList.of(value1, value2),
|
||||
ImmutableSet.of(value1, value2)),
|
||||
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));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -226,15 +280,39 @@ 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 Collections.max(
|
||||
Refaster.anyOf(
|
||||
Arrays.asList(value1, value2),
|
||||
ImmutableList.of(value1, value2),
|
||||
ImmutableSet.of(value1, 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))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -249,12 +327,17 @@ final class ComparatorRules {
|
||||
static final class MaxOfPairCustomOrder<T> {
|
||||
@BeforeTemplate
|
||||
T before(T value1, T value2, Comparator<T> cmp) {
|
||||
return Collections.max(
|
||||
Refaster.anyOf(
|
||||
Arrays.asList(value1, value2),
|
||||
ImmutableList.of(value1, value2),
|
||||
ImmutableSet.of(value1, value2)),
|
||||
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));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,6 +355,42 @@ final class OptionalRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when filtering a value of the
|
||||
* former type.
|
||||
*/
|
||||
static final class OptionalFilter<T> {
|
||||
@BeforeTemplate
|
||||
Optional<T> before(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return Refaster.anyOf(
|
||||
optional.stream().filter(predicate).findFirst(),
|
||||
optional.stream().filter(predicate).findAny());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<T> after(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return optional.filter(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when mapping a value of the
|
||||
* former type.
|
||||
*/
|
||||
// XXX: If `StreamMapFirst` also simplifies `.findAny()` expressions, then this rule can be
|
||||
// dropped in favour of `StreamMapFirst` and `OptionalIdentity`.
|
||||
static final class OptionalMap<S, T> {
|
||||
@BeforeTemplate
|
||||
Optional<? extends T> before(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.stream().map(function).findAny();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<? extends T> after(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.map(function);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Add a rule for:
|
||||
// `optional.flatMap(x -> pred(x) ? Optional.empty() : Optional.of(x))` and variants.
|
||||
// (Maybe canonicalize the inner expression. Maybe we rewrite already.)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.toOptional;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.function.Function.identity;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.MoreCollectors;
|
||||
@@ -18,17 +20,23 @@ import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
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;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.annotation.Description;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.annotation.Severity;
|
||||
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;
|
||||
|
||||
/** Refaster rules related to Reactor expressions and statements. */
|
||||
@@ -52,6 +60,45 @@ 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.
|
||||
@@ -71,6 +118,125 @@ 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
|
||||
* combining three or more reactive streams.
|
||||
*/
|
||||
static final class MonoZip<T, S> {
|
||||
@BeforeTemplate
|
||||
Mono<Tuple2<T, S>> before(Mono<T> mono, Mono<S> other) {
|
||||
return mono.zipWith(other);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<Tuple2<T, S>> after(Mono<T> mono, Mono<S> other) {
|
||||
return Mono.zip(mono, other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#zip(Mono, Mono)} with a chained combinator over a chained {@link
|
||||
* Mono#zipWith(Mono, BiFunction)}, as the former better conveys that the {@link Mono}s may be
|
||||
* subscribed to concurrently, and generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class MonoZipWithCombinator<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Mono<R> before(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
|
||||
return mono.zipWith(other, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<R> after(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
|
||||
return Mono.zip(mono, other).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zip(Publisher, Publisher)} over a chained {@link Flux#zipWith(Publisher)},
|
||||
* as the former better conveys that the {@link Publisher}s may be subscribed to concurrently, and
|
||||
* generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class FluxZip<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<Tuple2<T, S>> before(Flux<T> flux, Publisher<S> other) {
|
||||
return flux.zipWith(other);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<Tuple2<T, S>> after(Flux<T> flux, Publisher<S> other) {
|
||||
return Flux.zip(flux, other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zip(Publisher, Publisher)} with a chained combinator over a chained {@link
|
||||
* Flux#zipWith(Publisher, BiFunction)}, as the former better conveys that the {@link Publisher}s
|
||||
* may be subscribed to concurrently, and generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class FluxZipWithCombinator<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWith(other, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<R> after(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
|
||||
return Flux.zip(flux, other).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zipWithIterable(Iterable)} with a chained combinator over {@link
|
||||
* Flux#zipWithIterable(Iterable, BiFunction)}, as the former generally yields more readable code.
|
||||
*/
|
||||
static final class FluxZipWithIterable<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWithIterable(iterable, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
Flux<R> after(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWithIterable(iterable).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily defer {@link Mono#error(Throwable)}. */
|
||||
static final class MonoDeferredError<T> {
|
||||
@BeforeTemplate
|
||||
@@ -144,6 +310,60 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#take(long, boolean)} over {@link Flux#take(long)}.
|
||||
*
|
||||
* <p>In Reactor versions prior to 3.5.0, {@code Flux#take(long)} makes an unbounded request
|
||||
* upstream, and is equivalent to {@code Flux#take(long, false)}. In 3.5.0, the behavior of {@code
|
||||
* Flux#take(long)} will change to that of {@code Flux#take(long, true)}.
|
||||
*
|
||||
* <p>The intent with this Refaster rule is to get the new behavior before upgrading to Reactor
|
||||
* 3.5.0.
|
||||
*/
|
||||
// XXX: Drop this rule some time after upgrading to Reactor 3.6.0, or introduce a way to apply
|
||||
// this rule only when an older version of Reactor is on the classpath.
|
||||
// XXX: Once Reactor 3.6.0 is out, introduce a rule that rewrites code in the opposite direction.
|
||||
@Description(
|
||||
"Prior to Reactor 3.5.0, `take(n)` requests and unbounded number of elements upstream.")
|
||||
@Severity(WARNING)
|
||||
static final class FluxTake<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, long n) {
|
||||
return flux.take(n);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, long n) {
|
||||
return flux.take(n, /* limitRequest= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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
|
||||
@@ -174,7 +394,10 @@ 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));
|
||||
return Refaster.anyOf(
|
||||
flux.flatMap(function, 1),
|
||||
flux.flatMapSequential(function, 1),
|
||||
flux.map(function).concatMap(identity()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -191,7 +414,9 @@ 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.flatMap(function, 1, prefetch),
|
||||
flux.flatMapSequential(function, 1, prefetch),
|
||||
flux.map(function).concatMap(identity(), prefetch));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -243,16 +468,195 @@ final class ReactorRules {
|
||||
*/
|
||||
abstract static class MonoFlatMapToFlux<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract Mono<S> valueTransformation(@MayOptionallyUse T value);
|
||||
abstract Mono<S> transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Mono<T> mono) {
|
||||
return mono.flatMapMany(v -> valueTransformation(v));
|
||||
return mono.flatMapMany(v -> transformation(v));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Mono<T> mono) {
|
||||
return mono.flatMap(v -> valueTransformation(v)).flux();
|
||||
return mono.flatMap(v -> transformation(v)).flux();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#map(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class MonoMap<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono) {
|
||||
return mono.flatMap(x -> Mono.just(transformation(x)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Mono<T> mono) {
|
||||
return mono.map(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#map(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class FluxMap<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
|
||||
return Refaster.anyOf(
|
||||
flux.concatMap(x -> Mono.just(transformation(x))),
|
||||
flux.concatMap(x -> Flux.just(transformation(x))),
|
||||
flux.concatMap(x -> Mono.just(transformation(x)), prefetch),
|
||||
flux.concatMap(x -> Flux.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x))),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x))),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x)), delayUntilEnd, prefetch),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x)), delayUntilEnd, prefetch),
|
||||
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapDelayError(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapDelayError(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.switchMap(x -> Mono.just(transformation(x))),
|
||||
flux.switchMap(x -> Flux.just(transformation(x))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux) {
|
||||
return flux.map(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#mapNotNull(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class MonoMapNotNull<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono) {
|
||||
return mono.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)), Mono.fromSupplier(() -> transformation(x))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Mono<T> mono) {
|
||||
return mono.mapNotNull(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#mapNotNull(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class FluxMapNotNull<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Publisher<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
|
||||
return Refaster.anyOf(
|
||||
flux.concatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))),
|
||||
flux.concatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
prefetch),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
prefetch),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
delayUntilEnd,
|
||||
prefetch),
|
||||
flux.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency),
|
||||
flux.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapSequential(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency),
|
||||
flux.flatMapSequential(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.switchMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux) {
|
||||
return flux.mapNotNull(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +664,8 @@ final class ReactorRules {
|
||||
static final class MonoFlux<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Mono<T> mono) {
|
||||
return Flux.concat(mono);
|
||||
return Refaster.anyOf(
|
||||
mono.flatMapMany(Mono::just), mono.flatMapMany(Flux::just), Flux.concat(mono));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -277,9 +682,7 @@ final class ReactorRules {
|
||||
static final class MonoCollectToOptional<T> {
|
||||
@BeforeTemplate
|
||||
Mono<Optional<T>> before(Mono<T> mono) {
|
||||
return Refaster.anyOf(
|
||||
mono.map(Optional::of).defaultIfEmpty(Optional.empty()),
|
||||
mono.map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
|
||||
return mono.map(Optional::of).defaultIfEmpty(Optional.empty());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -315,6 +718,32 @@ 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.
|
||||
@@ -352,6 +781,42 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#doOnError(Class, Consumer)} over {@link Mono#doOnError(Predicate, Consumer)}
|
||||
* where possible.
|
||||
*/
|
||||
static final class MonoDoOnError<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return mono.doOnError(clazz::isInstance, onError);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return mono.doOnError(clazz, onError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#doOnError(Class, Consumer)} over {@link Flux#doOnError(Predicate, Consumer)}
|
||||
* where possible.
|
||||
*/
|
||||
static final class FluxDoOnError<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return flux.doOnError(clazz::isInstance, onError);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return flux.doOnError(clazz, onError);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete()} over more contrived alternatives. */
|
||||
static final class MonoOnErrorComplete<T> {
|
||||
@BeforeTemplate
|
||||
@@ -378,6 +843,225 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete(Class)}} over more contrived alternatives. */
|
||||
static final class MonoOnErrorCompleteClass<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz) {
|
||||
return Refaster.anyOf(
|
||||
mono.onErrorComplete(clazz::isInstance), mono.onErrorResume(clazz, e -> Mono.empty()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz) {
|
||||
return mono.onErrorComplete(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#onErrorComplete(Class)}} over more contrived alternatives. */
|
||||
static final class FluxOnErrorCompleteClass<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz) {
|
||||
return Refaster.anyOf(
|
||||
flux.onErrorComplete(clazz::isInstance),
|
||||
flux.onErrorResume(clazz, e -> Refaster.anyOf(Mono.empty(), Flux.empty())));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz) {
|
||||
return flux.onErrorComplete(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete(Predicate)}} over more contrived alternatives. */
|
||||
static final class MonoOnErrorCompletePredicate<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Predicate<? super Throwable> predicate) {
|
||||
return mono.onErrorResume(predicate, e -> Mono.empty());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Predicate<? super Throwable> predicate) {
|
||||
return mono.onErrorComplete(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#onErrorComplete(Predicate)}} over more contrived alternatives. */
|
||||
static final class FluxOnErrorCompletePredicate<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Predicate<? super Throwable> predicate) {
|
||||
return flux.onErrorResume(predicate, e -> Refaster.anyOf(Mono.empty(), Flux.empty()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Predicate<? super Throwable> predicate) {
|
||||
return flux.onErrorComplete(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorContinue(Class, BiConsumer)} over {@link
|
||||
* Mono#onErrorContinue(Predicate, BiConsumer)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorContinue<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return mono.onErrorContinue(clazz::isInstance, errorConsumer);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return mono.onErrorContinue(clazz, errorConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorContinue(Class, BiConsumer)} over {@link
|
||||
* Flux#onErrorContinue(Predicate, BiConsumer)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorContinue<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return flux.onErrorContinue(clazz::isInstance, errorConsumer);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return flux.onErrorContinue(clazz, errorConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorMap(Class, Function)} over {@link Mono#onErrorMap(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorMap<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return mono.onErrorMap(clazz::isInstance, mapper);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return mono.onErrorMap(clazz, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorMap(Class, Function)} over {@link Flux#onErrorMap(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorMap<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return flux.onErrorMap(clazz::isInstance, mapper);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return flux.onErrorMap(clazz, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorResume(Class, Function)} over {@link Mono#onErrorResume(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorResume<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
|
||||
return mono.onErrorResume(clazz::isInstance, fallback);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
|
||||
return mono.onErrorResume(clazz, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorResume(Class, Function)} over {@link Flux#onErrorResume(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorResume<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
|
||||
return flux.onErrorResume(clazz::isInstance, fallback);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
|
||||
return flux.onErrorResume(clazz, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorReturn(Class, Object)} over {@link Mono#onErrorReturn(Predicate,
|
||||
* Object)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorReturn<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return mono.onErrorReturn(clazz::isInstance, fallbackValue);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return mono.onErrorReturn(clazz, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorReturn(Class, Object)} over {@link Flux#onErrorReturn(Predicate,
|
||||
* Object)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorReturn<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return flux.onErrorReturn(clazz::isInstance, fallbackValue);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return flux.onErrorReturn(clazz, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link reactor.util.context.Context#empty()}} over more verbose alternatives. */
|
||||
// XXX: Consider introducing an `IsEmpty` matcher that identifies a wide range of guaranteed-empty
|
||||
// `Collection` and `Map` expressions.
|
||||
|
||||
@@ -77,6 +77,9 @@ 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) {
|
||||
@@ -168,8 +171,9 @@ final class StreamRules {
|
||||
* Where possible, clarify that a mapping operation will be applied only to a single stream
|
||||
* element.
|
||||
*/
|
||||
// XXX: Consider whether to have a similar rule for `.findAny()`. For parallel streams it
|
||||
// wouldn't be quite the same....
|
||||
// XXX: Implement a similar rule for `.findAny()`. For parallel streams this wouldn't be quite the
|
||||
// same, so such a rule requires a `Matcher` that heuristically identifies `Stream` expressions
|
||||
// with deterministic order.
|
||||
// XXX: This change is not equivalent for `null`-returning functions, as the original code throws
|
||||
// an NPE if the first element is `null`, while the latter yields an empty `Optional`.
|
||||
static final class StreamMapFirst<T, S> {
|
||||
|
||||
@@ -13,9 +13,11 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.chrono.ChronoLocalDate;
|
||||
import java.time.chrono.ChronoLocalDateTime;
|
||||
import java.time.chrono.ChronoZonedDateTime;
|
||||
@@ -64,7 +66,83 @@ final class TimeRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atOffset(ZoneOffset)} over the more verbose alternative. */
|
||||
/** Prefer {@link LocalDate#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalDateOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalDate before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalDate(),
|
||||
LocalDateTime.ofInstant(instant, zoneId).toLocalDate(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalDate());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalDate before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalDate();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalDate after(Instant instant, ZoneId zoneId) {
|
||||
return LocalDate.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link LocalDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalDateTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalDateTime(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalDateTime());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalDateTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalDateTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return LocalDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link LocalTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalTime before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalTime(),
|
||||
LocalDateTime.ofInstant(instant, zoneId).toLocalTime(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalTime(),
|
||||
OffsetTime.ofInstant(instant, zoneId).toLocalTime());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalTime after(Instant instant, ZoneId zoneId) {
|
||||
return LocalTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link OffsetDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class OffsetDateTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
OffsetDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return instant.atZone(zoneId).toOffsetDateTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
OffsetDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return OffsetDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atOffset(ZoneOffset)} over more verbose alternatives. */
|
||||
static final class InstantAtOffset {
|
||||
@BeforeTemplate
|
||||
OffsetDateTime before(Instant instant, ZoneOffset zoneOffset) {
|
||||
@@ -77,6 +155,37 @@ final class TimeRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link OffsetTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class OffsetTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
OffsetTime before(Instant instant, ZoneId zoneId) {
|
||||
return OffsetDateTime.ofInstant(instant, zoneId).toOffsetTime();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
OffsetTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toOffsetTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
OffsetTime after(Instant instant, ZoneId zoneId) {
|
||||
return OffsetTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atZone(ZoneId)} over more verbose alternatives. */
|
||||
static final class InstantAtZone {
|
||||
@BeforeTemplate
|
||||
ZonedDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return ZonedDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ZonedDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return instant.atZone(zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Use {@link Clock#systemUTC()} when possible. */
|
||||
static final class UtcClock {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -94,13 +94,11 @@ final class AutowiredConstructorTest {
|
||||
"",
|
||||
"interface Container {",
|
||||
" class A {",
|
||||
"",
|
||||
" @Deprecated",
|
||||
" A() {}",
|
||||
" }",
|
||||
"",
|
||||
" class B {",
|
||||
"",
|
||||
" B(String x) {}",
|
||||
" }",
|
||||
"}")
|
||||
|
||||
@@ -65,6 +65,23 @@ final class CollectorMutabilityTest {
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithoutGuavaOnClasspath() {
|
||||
compilationTestHelper
|
||||
.withClasspath()
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Collectors;",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.empty().collect(Collectors.toList());",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementFirstSuggestedFix() {
|
||||
refactoringTestHelper
|
||||
|
||||
@@ -76,8 +76,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
|
||||
@@ -33,6 +34,14 @@ final class FluxFlatMapUsageTest {
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).<String>flatMap(i -> Flux.just(String.valueOf(i)));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
|
||||
"",
|
||||
" Mono.just(1).flatMap(Mono::just);",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
@@ -71,9 +80,13 @@ final class FluxFlatMapUsageTest {
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final int MAX_CONCURRENCY = 8;",
|
||||
"",
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
@@ -81,12 +94,16 @@ final class FluxFlatMapUsageTest {
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final int MAX_CONCURRENCY = 8;",
|
||||
"",
|
||||
" void m() {",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,6 +120,8 @@ final class FluxFlatMapUsageTest {
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
@@ -115,8 +134,10 @@ final class FluxFlatMapUsageTest {
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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 IsInstanceLambdaUsageTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Stream.of(1).filter(i -> i instanceof Integer);",
|
||||
" Stream.of(2).filter(Integer.class::isInstance);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).filter(i -> i instanceof Integer);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).filter(Integer.class::isInstance);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
final class ScheduledTransactionTraceTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
@@ -43,6 +44,21 @@ final class ScheduledTransactionTraceTest {
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithoutNewRelicAgentApiOnClasspath() {
|
||||
compilationTestHelper
|
||||
.withClasspath(Scheduled.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,11 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TimeZoneUsageTest {
|
||||
private final CompilationTestHelper compilationHelper =
|
||||
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"X",
|
||||
containsPattern(
|
||||
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone"));
|
||||
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
@@ -26,7 +20,10 @@ final class TimeZoneUsageTest {
|
||||
"import java.time.LocalDate;",
|
||||
"import java.time.LocalDateTime;",
|
||||
"import java.time.LocalTime;",
|
||||
"import java.time.OffsetDateTime;",
|
||||
"import java.time.OffsetTime;",
|
||||
"import java.time.ZoneId;",
|
||||
"import java.time.ZonedDateTime;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
@@ -36,48 +33,69 @@ final class TimeZoneUsageTest {
|
||||
" Clock.offset(clock, Duration.ZERO);",
|
||||
" Clock.tick(clock, Duration.ZERO);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.systemUTC();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.systemDefaultZone();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.system(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickMillis(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickMinutes(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickSeconds(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" clock.getZone();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" clock.withZone(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Instant.now();",
|
||||
" // This is equivalent to `clock.instant()`, which is fine.",
|
||||
" Instant.now(clock);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now(UTC);",
|
||||
" }",
|
||||
"",
|
||||
" abstract class ForwardingClock extends Clock {",
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.superOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.Signatures;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class MoreTypesTest {
|
||||
@Test
|
||||
void matcher() {
|
||||
CompilationTestHelper.newInstance(SubtypeFlagger.class, getClass())
|
||||
.addSourceLines(
|
||||
"/A.java",
|
||||
"import java.util.Collection;",
|
||||
"import java.util.List;",
|
||||
"import java.util.Map;",
|
||||
"import java.util.Optional;",
|
||||
"import java.util.Set;",
|
||||
"",
|
||||
"class A<S, T> {",
|
||||
" void m() {",
|
||||
" Object object = factory();",
|
||||
" A a = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Number, ? super Number, Integer, ? super Integer]",
|
||||
" int integer = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [String]",
|
||||
" String string = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Optional]",
|
||||
" Optional rawOptional = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<S> optionalOfS = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<T> optionalOfT = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>, Optional<Number>]",
|
||||
" Optional<Number> optionalOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<Integer> optionalOfInteger = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection]",
|
||||
" Collection rawCollection = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
|
||||
" Collection<Number> collectionOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
|
||||
" Collection<Integer> collectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
|
||||
" Collection<Short> collectionOfShort = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection, List]",
|
||||
" List rawList = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>, List, List<?>,",
|
||||
" // List<Number>, List<? super Number>, List<? extends Number>, List<? super Integer>]",
|
||||
" List<Number> listOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>, List,",
|
||||
" // List<?>, List<? extends Number>, List<Integer>, List<? super Integer>, List<? extends",
|
||||
" // Integer>]",
|
||||
" List<Integer> listOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>, List,",
|
||||
" // List<?>, List<? extends Number>]",
|
||||
" List<Short> listOfShort = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection]",
|
||||
" Set rawSet = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
|
||||
" Set<Number> setOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
|
||||
" Set<Integer> setOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
|
||||
" Set<Short> setOfShort = factory();",
|
||||
"",
|
||||
" Map rawMap = factory();",
|
||||
" Map<Number, Collection<Number>> mapFromNumberToCollectionOfNumber = factory();",
|
||||
" Map<Number, Collection<Short>> mapFromNumberToCollectionOfShort = factory();",
|
||||
" Map<Number, Collection<Integer>> mapFromNumberToCollectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, Collection<Number>> mapFromStringToCollectionOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, Collection<Short>> mapFromStringToCollectionOfShort = factory();",
|
||||
" Map<String, Collection<Integer>> mapFromStringToCollectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, List<Number>> mapFromStringToListOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, List<Short>> mapFromStringToListOfShort = factory();",
|
||||
" Map<String, List<Integer>> mapFromStringToListOfInteger = factory();",
|
||||
" }",
|
||||
"",
|
||||
" private <T> T factory() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags method invocations that are a subtype of any type defined by
|
||||
* {@link #getTestTypes()}.
|
||||
*/
|
||||
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
|
||||
public static final class SubtypeFlagger extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
Type treeType = ASTHelpers.getType(tree);
|
||||
|
||||
List<String> matches = new ArrayList<>();
|
||||
|
||||
for (Supplier<Type> type : getTestTypes()) {
|
||||
Type testType = type.get(state);
|
||||
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
|
||||
matches.add(Signatures.prettyType(testType));
|
||||
}
|
||||
}
|
||||
|
||||
return matches.isEmpty()
|
||||
? 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"))))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.ClassTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
final class ThirdPartyLibraryTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass());
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowed() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesInSymtab() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.newrelic.api.agent.Agent;",
|
||||
"import org.assertj.core.api.Assertions;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
|
||||
"class A {",
|
||||
" void m(Class<?> clazz) {",
|
||||
" m(Assertions.class);",
|
||||
" m(ImmutableList.class);",
|
||||
" m(Agent.class);",
|
||||
" m(Flux.class);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesPartiallyOnClassPath() {
|
||||
compilationTestHelper
|
||||
.withClasspath(ImmutableList.class, Flux.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: true, NEW_RELIC_AGENT_API: false, REACTOR: true",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesNotOnClassPath() {
|
||||
compilationTestHelper
|
||||
.withClasspath()
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: false, NEW_RELIC_AGENT_API: false, REACTOR:",
|
||||
"// false",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void isIntroductionAllowedIgnoreClasspathCompat(boolean ignoreClassPath) {
|
||||
compilationTestHelper
|
||||
.setArgs("-XepOpt:ErrorProneSupport:IgnoreClasspathCompat=" + ignoreClassPath)
|
||||
.withClasspath(ImmutableList.class, Flux.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
String.format(
|
||||
"// BUG: Diagnostic contains: ASSERTJ: %s, GUAVA: true, NEW_RELIC_AGENT_API: %s, REACTOR: true",
|
||||
ignoreClassPath, ignoreClassPath),
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags classes with a diagnostics message that indicates, for each {@link ThirdPartyLibrary}
|
||||
* element, whether they can be used.
|
||||
*/
|
||||
@BugPattern(severity = ERROR, summary = "Interacts with `ThirdPartyLibrary` for testing purposes")
|
||||
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
return buildDescription(tree)
|
||||
.setMessage(
|
||||
Arrays.stream(ThirdPartyLibrary.values())
|
||||
.map(
|
||||
lib ->
|
||||
String.join(
|
||||
": ", lib.name(), String.valueOf(lib.isIntroductionAllowed(state))))
|
||||
.collect(joining(", ")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ final class RefasterRulesTest {
|
||||
JUnitRules.class,
|
||||
LongStreamRules.class,
|
||||
MapEntryRules.class,
|
||||
MapRules.class,
|
||||
MockitoRules.class,
|
||||
MultimapRules.class,
|
||||
NullRules.class,
|
||||
|
||||
@@ -2,18 +2,143 @@ 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 org.assertj.core.api.AbstractMapAssert;
|
||||
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 tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
|
||||
AbstractMapAssert<?, ?, Integer, Integer>
|
||||
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
|
||||
@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() {
|
||||
return assertThat(ImmutableMap.of(1, 2, 3, 4)).isEqualTo(ImmutableMap.of(1, 2, 3, 4));
|
||||
}
|
||||
|
||||
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
|
||||
MapAssert<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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,143 @@ 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 org.assertj.core.api.AbstractMapAssert;
|
||||
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 tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
|
||||
AbstractMapAssert<?, ?, Integer, Integer>
|
||||
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
|
||||
@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() {
|
||||
return assertThat(ImmutableMap.of(1, 2, 3, 4))
|
||||
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2, 3, 4));
|
||||
}
|
||||
|
||||
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
|
||||
MapAssert<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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,12 @@ 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;
|
||||
|
||||
@@ -23,7 +19,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<?> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(
|
||||
HashMap.class,
|
||||
HashSet.class,
|
||||
Iterables.class,
|
||||
Preconditions.class,
|
||||
@@ -43,14 +38,6 @@ 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());
|
||||
}
|
||||
@@ -95,14 +82,6 @@ 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")),
|
||||
|
||||
@@ -8,18 +8,13 @@ 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;
|
||||
|
||||
@@ -27,7 +22,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<?> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(
|
||||
HashMap.class,
|
||||
HashSet.class,
|
||||
Iterables.class,
|
||||
Preconditions.class,
|
||||
@@ -45,14 +39,6 @@ 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());
|
||||
}
|
||||
@@ -95,14 +81,6 @@ 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"),
|
||||
|
||||
@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(BigDecimal.valueOf(10), BigDecimal.valueOf(10L), new BigDecimal("10"));
|
||||
}
|
||||
|
||||
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
|
||||
return ImmutableSet.of(new BigDecimal(0), new BigDecimal(0L));
|
||||
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
|
||||
return ImmutableSet.of(new BigDecimal(2), new BigDecimal(2L), new BigDecimal(2.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(BigDecimal.TEN, BigDecimal.TEN, BigDecimal.TEN);
|
||||
}
|
||||
|
||||
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
|
||||
return ImmutableSet.of(BigDecimal.valueOf(0), BigDecimal.valueOf(0L));
|
||||
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
|
||||
return ImmutableSet.of(BigDecimal.valueOf(2), BigDecimal.valueOf(2L), BigDecimal.valueOf(2.0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,4 +186,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
? Optional.ofNullable(new LinkedList<String>().remove())
|
||||
: Optional.empty());
|
||||
}
|
||||
|
||||
void testCollectionForEach() {
|
||||
ImmutableSet.of(1).stream().forEach(String::valueOf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,4 +136,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,30 +4,43 @@ 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, identity());
|
||||
Arrays.class,
|
||||
Collections.class,
|
||||
ImmutableList.class,
|
||||
ImmutableSet.class,
|
||||
Stream.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());
|
||||
}
|
||||
|
||||
Comparator<String> testReverseOrder() {
|
||||
return Comparator.<String>naturalOrder().reversed();
|
||||
ImmutableSet<Comparator<String>> testReverseOrder() {
|
||||
return ImmutableSet.of(
|
||||
Collections.reverseOrder(),
|
||||
Collections.reverseOrder(String::compareTo),
|
||||
Collections.<String>reverseOrder(naturalOrder()),
|
||||
Comparator.<String>naturalOrder().reversed());
|
||||
}
|
||||
|
||||
ImmutableSet<Comparator<String>> testCustomComparator() {
|
||||
@@ -77,8 +90,24 @@ 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")));
|
||||
@@ -86,13 +115,27 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
|
||||
ImmutableSet<Object> testMinOfPairCustomOrder() {
|
||||
return ImmutableSet.of(
|
||||
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));
|
||||
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();
|
||||
}
|
||||
|
||||
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")));
|
||||
@@ -100,9 +143,13 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
|
||||
ImmutableSet<Object> testMaxOfPairCustomOrder() {
|
||||
return ImmutableSet.of(
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
BinaryOperator<String> testComparatorsMin() {
|
||||
|
||||
@@ -11,21 +11,32 @@ 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, identity());
|
||||
Arrays.class,
|
||||
Collections.class,
|
||||
ImmutableList.class,
|
||||
ImmutableSet.class,
|
||||
Stream.class,
|
||||
identity());
|
||||
}
|
||||
|
||||
ImmutableSet<Comparator<String>> testNaturalOrder() {
|
||||
return ImmutableSet.of(naturalOrder(), naturalOrder(), naturalOrder());
|
||||
return ImmutableSet.of(
|
||||
naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder());
|
||||
}
|
||||
|
||||
Comparator<String> testReverseOrder() {
|
||||
return reverseOrder();
|
||||
ImmutableSet<Comparator<String>> testReverseOrder() {
|
||||
return ImmutableSet.of(
|
||||
Comparator.reverseOrder(),
|
||||
Comparator.reverseOrder(),
|
||||
Comparator.reverseOrder(),
|
||||
Comparator.reverseOrder());
|
||||
}
|
||||
|
||||
ImmutableSet<Comparator<String>> testCustomComparator() {
|
||||
@@ -68,28 +79,64 @@ 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("a", "b"));
|
||||
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"));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testMinOfPairCustomOrder() {
|
||||
return ImmutableSet.of(
|
||||
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));
|
||||
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());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testMaxOfPairNaturalOrder() {
|
||||
return ImmutableSet.of(
|
||||
Comparators.max("a", "b"), Comparators.max("a", "b"), Comparators.max("a", "b"));
|
||||
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"));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testMaxOfPairCustomOrder() {
|
||||
return ImmutableSet.of(
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
BinaryOperator<String> testComparatorsMin() {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -109,4 +109,14 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Optional.of("baz").stream().min(String::compareTo),
|
||||
Optional.of("qux").stream().max(String::compareTo));
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<String>> testOptionalFilter() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").stream().filter(String::isEmpty).findFirst(),
|
||||
Optional.of("bar").stream().filter(String::isEmpty).findAny());
|
||||
}
|
||||
|
||||
Optional<String> testOptionalMap() {
|
||||
return Optional.of(1).stream().map(String::valueOf).findAny();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +103,13 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo"), Optional.of("bar"), Optional.of("baz"), Optional.of("qux"));
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<String>> testOptionalFilter() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").filter(String::isEmpty), Optional.of("bar").filter(String::isEmpty));
|
||||
}
|
||||
|
||||
Optional<String> testOptionalMap() {
|
||||
return Optional.of(1).map(String::valueOf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -15,6 +16,7 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -32,12 +34,52 @@ 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));
|
||||
}
|
||||
|
||||
Mono<String> testMonoZipWithCombinator() {
|
||||
return Mono.just("foo").zipWith(Mono.just(1), String::repeat);
|
||||
}
|
||||
|
||||
Flux<Tuple2<String, Integer>> testFluxZip() {
|
||||
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithCombinator() {
|
||||
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2), String::repeat);
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithIterable() {
|
||||
return Flux.just("foo", "bar").zipWithIterable(ImmutableSet.of(1, 2), String::repeat);
|
||||
}
|
||||
|
||||
Mono<Void> testMonoDeferredError() {
|
||||
return Mono.defer(() -> Mono.error(new IllegalStateException()));
|
||||
}
|
||||
@@ -58,6 +100,20 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.empty().then(Mono.just("foo"));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxTake() {
|
||||
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());
|
||||
}
|
||||
@@ -69,12 +125,16 @@ 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(1).flatMap(Mono::just, 1),
|
||||
Flux.just(2).flatMapSequential(Mono::just, 1),
|
||||
Flux.just(3).map(Mono::just).concatMap(identity()));
|
||||
}
|
||||
|
||||
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(1).flatMap(Mono::just, 1, 3),
|
||||
Flux.just(2).flatMapSequential(Mono::just, 1, 4),
|
||||
Flux.just(3).map(Mono::just).concatMap(identity(), 5));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxConcatMapIterable() {
|
||||
@@ -86,17 +146,82 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlatMapToFlux() {
|
||||
return Mono.just("foo").flatMapMany(s -> Mono.just(s + s));
|
||||
return Mono.just("foo").flatMapMany(s -> Mono.fromSupplier(() -> s + s));
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlux() {
|
||||
return Flux.concat(Mono.just("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
|
||||
ImmutableSet<Mono<String>> testMonoMap() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty()),
|
||||
Mono.just("bar").map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
|
||||
Mono.just("foo").flatMap(s -> Mono.just(s)),
|
||||
Mono.just("bar").flatMap(s -> Mono.just(s.substring(1))));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMap() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).concatMap(n -> Mono.just(n)),
|
||||
Flux.just(1).concatMap(n -> Flux.just(n * 2)),
|
||||
Flux.just(1).concatMap(n -> Mono.just(n), 3),
|
||||
Flux.just(1).concatMap(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.just(n)),
|
||||
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.just(n), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.just(n), 3),
|
||||
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).switchMap(n -> Mono.just(n)),
|
||||
Flux.just(1).switchMap(n -> Flux.just(n * 2)));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").flatMap(s -> Mono.justOrEmpty(s)),
|
||||
Mono.just("bar").flatMap(s -> Mono.fromSupplier(() -> s.substring(1))));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n)),
|
||||
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2)),
|
||||
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).switchMap(n -> Mono.justOrEmpty(n)),
|
||||
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")));
|
||||
}
|
||||
|
||||
Mono<Optional<String>> testMonoCollectToOptional() {
|
||||
return Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty());
|
||||
}
|
||||
|
||||
Mono<Number> testMonoCast() {
|
||||
@@ -107,6 +232,14 @@ 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)),
|
||||
@@ -119,6 +252,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoDoOnError() {
|
||||
return Mono.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxDoOnError() {
|
||||
return Flux.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorComplete() {
|
||||
return Mono.just(1).onErrorResume(e -> Mono.empty());
|
||||
}
|
||||
@@ -129,6 +270,63 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(2).onErrorResume(e -> Flux.empty()));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
|
||||
Mono.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
|
||||
Flux.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()),
|
||||
Flux.just(3).onErrorResume(AssertionError.class, e -> Flux.empty()));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorCompletePredicate() {
|
||||
return Mono.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty());
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty()),
|
||||
Flux.just(2).onErrorResume(e -> e.getCause() != null, e -> Flux.empty()));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorContinue() {
|
||||
return Mono.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorContinue() {
|
||||
return Flux.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorMap() {
|
||||
return Mono.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorMap() {
|
||||
return Flux.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorResume() {
|
||||
return Mono.just(1)
|
||||
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Mono.just(2));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorResume() {
|
||||
return Flux.just(1)
|
||||
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Flux.just(2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorReturn() {
|
||||
return Mono.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorReturn() {
|
||||
return Flux.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Context> testContextEmpty() {
|
||||
return ImmutableSet.of(Context.of(new HashMap<>()), Context.of(ImmutableMap.of()));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static com.google.common.collect.MoreCollectors.toOptional;
|
||||
import static java.util.function.Function.identity;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -14,9 +15,11 @@ import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.function.TupleUtils;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -34,12 +37,55 @@ 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));
|
||||
}
|
||||
|
||||
Mono<String> testMonoZipWithCombinator() {
|
||||
return Mono.zip(Mono.just("foo"), Mono.just(1)).map(TupleUtils.function(String::repeat));
|
||||
}
|
||||
|
||||
Flux<Tuple2<String, Integer>> testFluxZip() {
|
||||
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithCombinator() {
|
||||
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2))
|
||||
.map(TupleUtils.function(String::repeat));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithIterable() {
|
||||
return Flux.just("foo", "bar")
|
||||
.zipWithIterable(ImmutableSet.of(1, 2))
|
||||
.map(function(String::repeat));
|
||||
}
|
||||
|
||||
Mono<Void> testMonoDeferredError() {
|
||||
return Mono.error(() -> new IllegalStateException());
|
||||
}
|
||||
@@ -60,6 +106,19 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.empty().thenReturn("foo");
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxTake() {
|
||||
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);
|
||||
}
|
||||
@@ -69,12 +128,17 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
|
||||
return ImmutableSet.of(Flux.just(1).concatMap(Mono::just), Flux.just(2).concatMap(Mono::just));
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).concatMap(Mono::just),
|
||||
Flux.just(2).concatMap(Mono::just),
|
||||
Flux.just(3).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(1).concatMap(Mono::just, 3),
|
||||
Flux.just(2).concatMap(Mono::just, 4),
|
||||
Flux.just(3).concatMap(Mono::just, 5));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxConcatMapIterable() {
|
||||
@@ -86,17 +150,77 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlatMapToFlux() {
|
||||
return Mono.just("foo").flatMap(s -> Mono.just(s + s)).flux();
|
||||
return Mono.just("foo").flatMap(s -> Mono.fromSupplier(() -> s + s)).flux();
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlux() {
|
||||
return Mono.just("foo").flux();
|
||||
ImmutableSet<Mono<String>> testMonoMap() {
|
||||
return ImmutableSet.of(Mono.just("foo").map(s -> s), Mono.just("bar").map(s -> s.substring(1)));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
|
||||
ImmutableSet<Flux<Integer>> testFluxMap() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").flux().collect(toOptional()),
|
||||
Mono.just("bar").flux().collect(toOptional()));
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").mapNotNull(s -> s), Mono.just("bar").mapNotNull(s -> s.substring(1)));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
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());
|
||||
}
|
||||
|
||||
Mono<Optional<String>> testMonoCollectToOptional() {
|
||||
return Mono.just("foo").flux().collect(toOptional());
|
||||
}
|
||||
|
||||
Mono<Number> testMonoCast() {
|
||||
@@ -107,6 +231,14 @@ 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()),
|
||||
@@ -119,6 +251,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoDoOnError() {
|
||||
return Mono.just(1).doOnError(IllegalArgumentException.class, e -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxDoOnError() {
|
||||
return Flux.just(1).doOnError(IllegalArgumentException.class, e -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorComplete() {
|
||||
return Mono.just(1).onErrorComplete();
|
||||
}
|
||||
@@ -127,6 +267,61 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(Flux.just(1).onErrorComplete(), Flux.just(2).onErrorComplete());
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).onErrorComplete(IllegalArgumentException.class),
|
||||
Mono.just(2).onErrorComplete(IllegalStateException.class));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(IllegalArgumentException.class),
|
||||
Flux.just(2).onErrorComplete(IllegalStateException.class),
|
||||
Flux.just(3).onErrorComplete(AssertionError.class));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorCompletePredicate() {
|
||||
return Mono.just(1).onErrorComplete(e -> e.getCause() == null);
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(e -> e.getCause() == null),
|
||||
Flux.just(2).onErrorComplete(e -> e.getCause() != null));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorContinue() {
|
||||
return Mono.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorContinue() {
|
||||
return Flux.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorMap() {
|
||||
return Mono.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorMap() {
|
||||
return Flux.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorResume() {
|
||||
return Mono.just(1).onErrorResume(IllegalArgumentException.class, e -> Mono.just(2));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorResume() {
|
||||
return Flux.just(1).onErrorResume(IllegalArgumentException.class, e -> Flux.just(2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorReturn() {
|
||||
return Mono.just(1).onErrorReturn(IllegalArgumentException.class, 2);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorReturn() {
|
||||
return Flux.just(1).onErrorReturn(IllegalArgumentException.class, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Context> testContextEmpty() {
|
||||
return ImmutableSet.of(Context.empty(), Context.empty());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
ZoneId.from(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDate> testLocalDateOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDate(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDate(),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDate(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalDate());
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDateTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDateTime(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDateTime());
|
||||
}
|
||||
|
||||
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalTime(),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalTime(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalTime(),
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX).toLocalTime());
|
||||
}
|
||||
|
||||
OffsetDateTime testOffsetDateTimeOfInstant() {
|
||||
return Instant.EPOCH.atZone(ZoneOffset.UTC).toOffsetDateTime();
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")).toOffsetTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toOffsetTime());
|
||||
}
|
||||
|
||||
ZonedDateTime testInstantAtZone() {
|
||||
return ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
Clock testUtcClock() {
|
||||
return Clock.system(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDate> testLocalDateOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.MIN));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX));
|
||||
}
|
||||
|
||||
OffsetDateTime testOffsetDateTimeOfInstant() {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return Instant.EPOCH.atOffset(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
ZonedDateTime testInstantAtZone() {
|
||||
return Instant.EPOCH.atZone(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
Clock testUtcClock() {
|
||||
return Clock.systemUTC();
|
||||
}
|
||||
|
||||
121
pom.xml
121
pom.xml
@@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Picnic :: Error Prone Support</name>
|
||||
@@ -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</version.auto-value>
|
||||
<version.auto-value>1.10.1</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,18 +154,10 @@
|
||||
<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.8.1</version.mockito>
|
||||
<version.mockito>4.9.0</version.mockito>
|
||||
<version.nopen-checker>1.0.1</version.nopen-checker>
|
||||
<version.nullaway>0.10.2</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.nullaway>0.10.5</version.nullaway>
|
||||
<version.pitest-git>1.0.3</version.pitest-git>
|
||||
<version.surefire>2.22.2</version.surefire>
|
||||
</properties>
|
||||
|
||||
@@ -219,7 +211,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>2.13.4.20221013</version>
|
||||
<version>2.14.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -277,21 +269,7 @@
|
||||
<dependency>
|
||||
<groupId>com.newrelic.agent.java</groupId>
|
||||
<artifactId>newrelic-api</artifactId>
|
||||
<version>7.10.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>
|
||||
<version>7.11.1</version>
|
||||
</dependency>
|
||||
<!-- Specified as a workaround for
|
||||
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
|
||||
@@ -303,7 +281,7 @@
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-bom</artifactId>
|
||||
<version>2020.0.24</version>
|
||||
<version>2022.0.0</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -315,12 +293,12 @@
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.8</version>
|
||||
<version>1.6.9</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<version>2.2.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
@@ -365,7 +343,7 @@
|
||||
<dependency>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-qual</artifactId>
|
||||
<version>3.26.0</version>
|
||||
<version>3.28.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@@ -399,19 +377,19 @@
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.3</version>
|
||||
<version>2.0.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>5.3.23</version>
|
||||
<version>5.3.24</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<version>2.7.5</version>
|
||||
<version>2.7.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
@@ -451,6 +429,16 @@
|
||||
</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>
|
||||
@@ -783,7 +771,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.3.4</version>
|
||||
<version>10.5.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.spring.nohttp</groupId>
|
||||
@@ -844,20 +832,6 @@
|
||||
<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>
|
||||
@@ -896,16 +870,16 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<version>3.4.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>
|
||||
@@ -984,7 +958,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.1.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -1216,7 +1190,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>tidy-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
@@ -1229,7 +1203,7 @@
|
||||
<plugin>
|
||||
<groupId>org.gaul</groupId>
|
||||
<artifactId>modernizer-maven-plugin</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<version>2.5.0</version>
|
||||
<configuration>
|
||||
<exclusionPatterns>
|
||||
<!-- The plugin suggests replacing usages of
|
||||
@@ -1265,11 +1239,19 @@
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.9.8</version>
|
||||
<version>1.10.3</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. -->
|
||||
@@ -1278,6 +1260,21 @@
|
||||
<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>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-runner</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-support</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.4.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user