mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 15:49:33 +00:00
Compare commits
152 Commits
sschroever
...
sschroever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
196edf9118 | ||
|
|
0109632b70 | ||
|
|
1650b6b00d | ||
|
|
967a446909 | ||
|
|
4d30329448 | ||
|
|
66967fb903 | ||
|
|
e6854a9147 | ||
|
|
1ca0f536c4 | ||
|
|
d569156a6b | ||
|
|
e7c3d39059 | ||
|
|
9d66379486 | ||
|
|
d01002de06 | ||
|
|
0c857b3d90 | ||
|
|
cc36aa993c | ||
|
|
379bbf3f83 | ||
|
|
a0b1f7091e | ||
|
|
626246bcc0 | ||
|
|
22272d6059 | ||
|
|
ff3be8ae3f | ||
|
|
7c2078b771 | ||
|
|
7529b99251 | ||
|
|
a966c1a17a | ||
|
|
cc3e30f253 | ||
|
|
b73b05a5c4 | ||
|
|
74de2535f5 | ||
|
|
f0ff5c3fd6 | ||
|
|
738a6706fd | ||
|
|
fd97ac3676 | ||
|
|
5b011312e1 | ||
|
|
ff3f963820 | ||
|
|
94edab3b4f | ||
|
|
276529fd34 | ||
|
|
f5022efe68 | ||
|
|
49e7313e1c | ||
|
|
b3462b0a1e | ||
|
|
a5b71410ae | ||
|
|
6b73f03b22 | ||
|
|
5d120252ac | ||
|
|
351b886e6c | ||
|
|
ad5dd92b9a | ||
|
|
e9a5295e80 | ||
|
|
7ae9356c9e | ||
|
|
5618de49f4 | ||
|
|
17c7b396d2 | ||
|
|
eafb73814a | ||
|
|
7793006b5e | ||
|
|
7733ceaaec | ||
|
|
53e0a0cb41 | ||
|
|
1d25b78c4c | ||
|
|
b70da48d70 | ||
|
|
01fd608aac | ||
|
|
dd0369a645 | ||
|
|
74b1104890 | ||
|
|
a230bbff12 | ||
|
|
100305ed1f | ||
|
|
96c836e6a9 | ||
|
|
97acfbc3bc | ||
|
|
9ce7c6b40b | ||
|
|
57a43a1c85 | ||
|
|
89c4969a31 | ||
|
|
dbd4853e3e | ||
|
|
62a7dacf4d | ||
|
|
d23684ee6d | ||
|
|
fd150af6c6 | ||
|
|
a1a865b87a | ||
|
|
64000ebb07 | ||
|
|
6122d7a39f | ||
|
|
ca01bbb74a | ||
|
|
4ce9f92489 | ||
|
|
e8d14993a1 | ||
|
|
88860800b1 | ||
|
|
e76dd5c31b | ||
|
|
701db7d61e | ||
|
|
8781a9ede5 | ||
|
|
dc14c4e970 | ||
|
|
a435b657ae | ||
|
|
00edc5ea1f | ||
|
|
7383a11da7 | ||
|
|
e4e17664d0 | ||
|
|
5972a8e225 | ||
|
|
01b30d767b | ||
|
|
c2426d3a8d | ||
|
|
8130ddf59c | ||
|
|
c3b950f114 | ||
|
|
fa026de336 | ||
|
|
2ff3095fca | ||
|
|
2b2d9f498e | ||
|
|
2a5dd7d56d | ||
|
|
dd021b3757 | ||
|
|
c77cc9a35d | ||
|
|
7a7a09d208 | ||
|
|
62077dacbb | ||
|
|
3a76f91d18 | ||
|
|
82c0dd95ce | ||
|
|
a424a3e949 | ||
|
|
3f530475ab | ||
|
|
24621b7c20 | ||
|
|
746e757e16 | ||
|
|
df42013d5b | ||
|
|
0336626dc9 | ||
|
|
4e2ceeb252 | ||
|
|
05809b1c85 | ||
|
|
75679396df | ||
|
|
f1882caf88 | ||
|
|
605d045760 | ||
|
|
b492a4afd3 | ||
|
|
583e51c9b0 | ||
|
|
9ada0783dd | ||
|
|
7ed3e63d2e | ||
|
|
465f83b855 | ||
|
|
24f8b2f499 | ||
|
|
f466c75bf3 | ||
|
|
c48db5202c | ||
|
|
629cb577ea | ||
|
|
b13d445e56 | ||
|
|
b1683a31e9 | ||
|
|
ffe4db2cf8 | ||
|
|
da5eea8329 | ||
|
|
5596b584ac | ||
|
|
4800522a09 | ||
|
|
9a77a3f35f | ||
|
|
ef75b80e7b | ||
|
|
7c618f7e2d | ||
|
|
c03ead9e5d | ||
|
|
8fd87a5b6b | ||
|
|
82025a729f | ||
|
|
7d912d1b04 | ||
|
|
a726514e98 | ||
|
|
aa7de5777c | ||
|
|
6e6efb3c13 | ||
|
|
03fd050eae | ||
|
|
d1467e0424 | ||
|
|
a2e32ed147 | ||
|
|
258708e6a8 | ||
|
|
67106a9725 | ||
|
|
f979576ab5 | ||
|
|
aa3fd03569 | ||
|
|
5602251667 | ||
|
|
05cb6387b9 | ||
|
|
4e242a5b11 | ||
|
|
87297c9a1d | ||
|
|
15ff85c4ea | ||
|
|
2c8469fa93 | ||
|
|
f4e0fd2cc3 | ||
|
|
34fd692064 | ||
|
|
26bc7e6e8e | ||
|
|
56cb86a7e1 | ||
|
|
19cd802ec5 | ||
|
|
1fabb26ed2 | ||
|
|
c1ced1e962 | ||
|
|
030ed25be8 | ||
|
|
ee0c04a44e |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -42,7 +42,7 @@ Please replace this sentence with log output, if applicable.
|
||||
<!-- Please complete the following information: -->
|
||||
|
||||
- Operating system (e.g. MacOS Monterey).
|
||||
- Java version (i.e. `java --version`, e.g. `17.0.7`).
|
||||
- Java version (i.e. `java --version`, e.g. `17.0.8`).
|
||||
- Error Prone version (e.g. `2.18.0`).
|
||||
- Error Prone Support version (e.g. `0.9.0`).
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 11.0.19, 17.0.7, 20.0.1 ]
|
||||
jdk: [ 11.0.20, 17.0.8, 21.0.0 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-12
|
||||
jdk: 17.0.7
|
||||
jdk: 17.0.8
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.7
|
||||
jdk: 17.0.8
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -31,11 +31,11 @@ jobs:
|
||||
# additionally enabling all checks defined in this project and any Error
|
||||
# Prone checks available only from other artifact repositories.
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
distribution: ${{ matrix.distribution }}
|
||||
12
.github/workflows/codeql.yml
vendored
12
.github/workflows/codeql.yml
vendored
@@ -22,23 +22,23 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 17.0.7
|
||||
java-version: 17.0.8
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
|
||||
uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Perform minimal build
|
||||
if: matrix.language == 'java'
|
||||
run: mvn -T1C clean install -DskipTests -Dverification.skip
|
||||
run: mvn -T1C clean package -DskipTests -Dverification.skip
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
|
||||
uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
|
||||
@@ -12,15 +12,15 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@250fcd6a742febb1123a77a841497ccaa8b9e939 # v1.152.0
|
||||
- uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@f156874f8191504dae5b037505266ed5dda6c382 # v3.0.6
|
||||
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
|
||||
- name: Generate documentation
|
||||
run: ./generate-docs.sh
|
||||
- name: Build website with Jekyll
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
# "Refaster rules" terminology on our website and in the code.
|
||||
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
|
||||
- name: Upload website as artifact
|
||||
uses: actions/upload-pages-artifact@a753861a5debcf57bf8b404356158c8e1e33150c # v2.0.0
|
||||
uses: actions/upload-pages-artifact@0252fc4ba7626f0298f0cf00902a25c6afc77fa8 # v3.0.0
|
||||
with:
|
||||
path: ./website/_site
|
||||
deploy:
|
||||
@@ -48,4 +48,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@9dbe3824824f8a1377b8e298bafde1a50ede43e5 # v2.0.4
|
||||
uses: actions/deploy-pages@7a9bd943aa5e5175aeb8502edcc6c1c02d398e10 # v4.0.2
|
||||
6
.github/workflows/openssf-scorecard.yml
vendored
6
.github/workflows/openssf-scorecard.yml
vendored
@@ -21,16 +21,16 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run OpenSSF Scorecard analysis
|
||||
uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: ${{ github.ref == 'refs/heads/master' }}
|
||||
- name: Update GitHub's code scanning dashboard
|
||||
uses: github/codeql-action/upload-sarif@a09933a12a80f87b87005513f0abb1494c27a716 # v2.21.4
|
||||
uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
8
.github/workflows/pitest-analyze-pr.yml
vendored
8
.github/workflows/pitest-analyze-pr.yml
vendored
@@ -12,14 +12,14 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 17.0.7
|
||||
java-version: 17.0.8
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Run Pitest
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- 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@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
with:
|
||||
name: pitest-reports
|
||||
path: ./target/pit-reports-ci
|
||||
|
||||
8
.github/workflows/pitest-update-pr.yml
vendored
8
.github/workflows/pitest-update-pr.yml
vendored
@@ -20,17 +20,17 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 17.0.7
|
||||
java-version: 17.0.8
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Download Pitest analysis artifact
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d # v3.0.0
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
name: pitest-reports
|
||||
|
||||
43
.github/workflows/run-integration-tests.yml
vendored
Normal file
43
.github/workflows/run-integration-tests.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# If requested by means of a pull request comment, runs integration tests
|
||||
# against the project, using the code found on the pull request branch.
|
||||
# XXX: Generalize this to a matrix build of multiple integration tests,
|
||||
# possibly using multiple JDK or OS versions.
|
||||
# XXX: Investigate whether the comment can specify which integration tests run
|
||||
# run. See this example of a dynamic build matrix:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object
|
||||
name: "Integration tests"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [ created ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
run-integration-tests:
|
||||
name: On-demand integration test
|
||||
if: |
|
||||
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: refs/pull/${{ github.event.issue.number }}/head
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 17.0.8
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Install project to local Maven repository
|
||||
run: mvn -T1C install -DskipTests -Dverification.skip
|
||||
- name: Run integration test
|
||||
run: xvfb-run ./integration-tests/checkstyle-10.12.4.sh "${{ runner.temp }}/artifacts"
|
||||
- name: Upload artifacts on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0
|
||||
with:
|
||||
name: integration-test-checkstyle-10.12.4
|
||||
path: "${{ runner.temp }}/artifacts"
|
||||
- name: Remove installed project artifacts
|
||||
run: mvn build-helper:remove-project-artifact
|
||||
9
.github/workflows/sonarcloud.yml
vendored
9
.github/workflows/sonarcloud.yml
vendored
@@ -11,19 +11,22 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
analyze:
|
||||
# Analysis of code in forked repositories is skipped, as such workflow runs
|
||||
# do not have access to the requisite secrets.
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@cd89f46ac9d01407894225f350157564c9c7cee2 # v3.12.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
java-version: 17.0.7
|
||||
java-version: 17.0.8
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Create missing `test` directory
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@
|
||||
target
|
||||
*.iml
|
||||
*.swp
|
||||
|
||||
# The Git repositories checked out by the integration test framework.
|
||||
integration-tests/.repos
|
||||
|
||||
13
README.md
13
README.md
@@ -15,9 +15,11 @@ focussing on maintainability, consistency and avoidance of common pitfalls.
|
||||
> Error Prone is a static analysis tool for Java that catches common
|
||||
> programming mistakes at compile-time.
|
||||
|
||||
Read more on how Picnic uses Error Prone (Support) in the blog post [_Picnic
|
||||
loves Error Prone: producing high-quality and consistent Java
|
||||
code_][picnic-blog-ep-post].
|
||||
To learn more about Error Prone (Support), how you can start using Error Prone
|
||||
in practice, and how we use it at Picnic, watch the conference talk
|
||||
[_Automating away bugs with Error Prone in practice_][conference-talk]. Also
|
||||
consider checking out the blog post [_Picnic 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]
|
||||
@@ -265,6 +267,7 @@ channel; please see our [security policy][security] for details.
|
||||
[bug-checks-identity-conversion]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java
|
||||
[codeql-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/codeql.yml/badge.svg?branch=master&event=push
|
||||
[codeql-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/codeql.yml?query=branch:master+event:push
|
||||
[conference-talk]: https://www.youtube.com/watch?v=-47WD-3wKBs
|
||||
[contributing]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md
|
||||
[contributing-pull-request]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md#-opening-a-pull-request
|
||||
[error-prone-bugchecker]: https://github.com/google/error-prone/blob/master/check_api/src/main/java/com/google/errorprone/bugpatterns/BugChecker.java
|
||||
@@ -274,8 +277,8 @@ channel; please see our [security policy][security] for details.
|
||||
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
|
||||
[error-prone-orig-repo]: https://github.com/google/error-prone
|
||||
[error-prone-pull-3301]: https://github.com/google/error-prone/pull/3301
|
||||
[github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml/badge.svg
|
||||
[github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml?query=branch:master&event=push
|
||||
[github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yml/badge.svg
|
||||
[github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yml?query=branch:master&event=push
|
||||
[google-java-format]: https://github.com/google/google-java-format
|
||||
[idea-288052]: https://youtrack.jetbrains.com/issue/IDEA-288052
|
||||
[license-badge]: https://img.shields.io/github/license/PicnicSupermarket/error-prone-support
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Arcmutate license for Error Prone Support, requested by sending an email to
|
||||
# support@arcmutate.com.
|
||||
expires=07/11/2023
|
||||
expires=27/10/2025
|
||||
keyVersion=1
|
||||
signature=MhZxMbnO6UovNfllM0JuVWkZyvRT3/G5o/uT0Mm36c7200VpZNVu03gTAGivnl9W5RzvZhfpIHccuQ5ctjQkrqhsFSrl4fyqPqu3y5V2fsHIdFXP/G72EGj6Kay9ndLpaEHalqE0bEwxdnHMzEYq5y3O9vUPv8MhUl57xk+rvBo\=
|
||||
signature=aQLVt0J//7nXDAlJuBe9ru7Dq6oApcLh17rxsgHoJqBkUCd2zvrtRxkcPm5PmF1I8gBDT9PHb+kTf6Kcfz+D1fT0wRrZv38ip56521AQPD6zjRSqak9O2Gisjo+QTPMD7oS009aSWKzQ1SMdtuZ+KIRvw4Gl+frtYzexqnGWEUQ\=
|
||||
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.13.1-SNAPSHOT</version>
|
||||
<version>0.14.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>documentation-support</artifactId>
|
||||
|
||||
@@ -5,17 +5,19 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.auto.common.AnnotationMirrors;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.BugPattern.SeverityLevel;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.tools.javac.code.Attribute;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
|
||||
|
||||
@@ -23,29 +25,39 @@ import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocume
|
||||
* An {@link Extractor} that describes how to extract data from a {@code @BugPattern} annotation.
|
||||
*/
|
||||
@Immutable
|
||||
final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
|
||||
@Override
|
||||
public BugPatternDocumentation extract(ClassTree tree, Context context) {
|
||||
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
|
||||
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
|
||||
requireNonNull(annotation, "BugPattern annotation must be present");
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
|
||||
/** Instantiates a new {@link BugPatternExtractor} instance. */
|
||||
public BugPatternExtractor() {}
|
||||
|
||||
return new AutoValue_BugPatternExtractor_BugPatternDocumentation(
|
||||
symbol.getQualifiedName().toString(),
|
||||
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
|
||||
ImmutableList.copyOf(annotation.altNames()),
|
||||
annotation.link(),
|
||||
ImmutableList.copyOf(annotation.tags()),
|
||||
annotation.summary(),
|
||||
annotation.explanation(),
|
||||
annotation.severity(),
|
||||
annotation.disableable(),
|
||||
annotation.documentSuppression() ? getSuppressionAnnotations(tree) : ImmutableList.of());
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "bugpattern";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canExtract(ClassTree tree) {
|
||||
return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName());
|
||||
public Optional<BugPatternDocumentation> tryExtract(ClassTree tree, VisitorState state) {
|
||||
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
|
||||
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
|
||||
if (annotation == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
new AutoValue_BugPatternExtractor_BugPatternDocumentation(
|
||||
symbol.getQualifiedName().toString(),
|
||||
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
|
||||
ImmutableList.copyOf(annotation.altNames()),
|
||||
annotation.link(),
|
||||
ImmutableList.copyOf(annotation.tags()),
|
||||
annotation.summary(),
|
||||
annotation.explanation(),
|
||||
annotation.severity(),
|
||||
annotation.disableable(),
|
||||
annotation.documentSuppression()
|
||||
? getSuppressionAnnotations(tree)
|
||||
: ImmutableList.of()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.function.Predicate.not;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from classes that test a {@code
|
||||
* BugChecker}.
|
||||
*/
|
||||
// XXX: Handle other methods from `{BugCheckerRefactoring,Compilation}TestHelper`:
|
||||
// - Indicate which custom arguments are specified, if any.
|
||||
// - For replacement tests, indicate which `FixChooser` is used.
|
||||
// - ... (We don't use all optional features; TBD what else to support.)
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
|
||||
public BugPatternTestExtractor() {}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "bugpattern-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
BugPatternTestCollector collector = new BugPatternTestCollector();
|
||||
|
||||
collector.scan(tree, state);
|
||||
|
||||
return Optional.of(collector.getCollectedTests())
|
||||
.filter(not(ImmutableList::isEmpty))
|
||||
.map(
|
||||
tests ->
|
||||
new AutoValue_BugPatternTestExtractor_TestCases(
|
||||
ASTHelpers.getSymbol(tree).className(), tests));
|
||||
}
|
||||
|
||||
private static final class BugPatternTestCollector
|
||||
extends TreeScanner<@Nullable Void, VisitorState> {
|
||||
private static final Matcher<ExpressionTree> COMPILATION_HELPER_DO_TEST =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
|
||||
.named("doTest");
|
||||
private static final Matcher<ExpressionTree> TEST_HELPER_NEW_INSTANCE =
|
||||
staticMethod()
|
||||
.onDescendantOfAny(
|
||||
"com.google.errorprone.CompilationTestHelper",
|
||||
"com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("newInstance")
|
||||
.withParameters(Class.class.getCanonicalName(), Class.class.getCanonicalName());
|
||||
private static final Matcher<ExpressionTree> IDENTIFICATION_SOURCE_LINES =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
|
||||
.named("addSourceLines");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_DO_TEST =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("doTest");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_EXPECT_UNCHANGED =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.named("expectUnchanged");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_OUTPUT_SOURCE_LINES =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.namedAnyOf("addOutputLines", "expectUnchanged");
|
||||
|
||||
private final List<TestCase> collectedTestCases = new ArrayList<>();
|
||||
|
||||
private ImmutableList<TestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedTestCases);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) {
|
||||
boolean isReplacementTest = REPLACEMENT_DO_TEST.matches(node, state);
|
||||
if (isReplacementTest || COMPILATION_HELPER_DO_TEST.matches(node, state)) {
|
||||
getClassUnderTest(node, state)
|
||||
.ifPresent(
|
||||
classUnderTest -> {
|
||||
List<TestEntry> entries = new ArrayList<>();
|
||||
if (isReplacementTest) {
|
||||
extractReplacementTestCases(node, entries, state);
|
||||
} else {
|
||||
extractIdentificationTestCases(node, entries, state);
|
||||
}
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
collectedTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_TestCase(
|
||||
classUnderTest, ImmutableList.copyOf(entries).reverse()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.visitMethodInvocation(node, state);
|
||||
}
|
||||
|
||||
private static Optional<String> getClassUnderTest(
|
||||
MethodInvocationTree tree, VisitorState state) {
|
||||
if (TEST_HELPER_NEW_INSTANCE.matches(tree, state)) {
|
||||
return Optional.ofNullable(ASTHelpers.getSymbol(tree.getArguments().get(0)))
|
||||
.filter(s -> !s.type.allparams().isEmpty())
|
||||
.map(s -> s.type.allparams().get(0).tsym.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
return receiver instanceof MethodInvocationTree
|
||||
? getClassUnderTest((MethodInvocationTree) receiver, state)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private static void extractIdentificationTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) {
|
||||
String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
|
||||
Optional<String> sourceCode =
|
||||
getSourceCode(tree).filter(s -> s.contains("// BUG: Diagnostic"));
|
||||
if (path != null && sourceCode.isPresent()) {
|
||||
sink.add(
|
||||
new AutoValue_BugPatternTestExtractor_IdentificationTestEntry(
|
||||
path, sourceCode.orElseThrow()));
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree) {
|
||||
extractIdentificationTestCases((MethodInvocationTree) receiver, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractReplacementTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) {
|
||||
/*
|
||||
* Retrieve the method invocation that contains the input source code. Note that this cast
|
||||
* is safe, because this code is guarded by an earlier call to `#getClassUnderTest(..)`,
|
||||
* which ensures that `tree` is part of a longer method invocation chain.
|
||||
*/
|
||||
MethodInvocationTree inputTree = (MethodInvocationTree) requireNonNull(ASTHelpers.getReceiver(tree));
|
||||
|
||||
String path = ASTHelpers.constValue(inputTree.getArguments().get(0), String.class);
|
||||
Optional<String> inputCode = getSourceCode(inputTree);
|
||||
if (path != null && inputCode.isPresent()) {
|
||||
Optional<String> outputCode =
|
||||
REPLACEMENT_EXPECT_UNCHANGED.matches(tree, state) ? inputCode : getSourceCode(tree);
|
||||
|
||||
if (outputCode.isPresent() && !inputCode.equals(outputCode)) {
|
||||
sink.add(
|
||||
new AutoValue_BugPatternTestExtractor_ReplacementTestEntry(
|
||||
path, inputCode.orElseThrow(), outputCode.orElseThrow()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree) {
|
||||
extractReplacementTestCases((MethodInvocationTree) receiver, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: This logic is duplicated in `ErrorProneTestSourceFormat`. Can we do better?
|
||||
private static Optional<String> getSourceCode(MethodInvocationTree tree) {
|
||||
List<? extends ExpressionTree> sourceLines =
|
||||
tree.getArguments().subList(1, tree.getArguments().size());
|
||||
StringBuilder source = new StringBuilder();
|
||||
|
||||
for (ExpressionTree sourceLine : sourceLines) {
|
||||
String value = ASTHelpers.constValue(sourceLine, String.class);
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
source.append(value).append('\n');
|
||||
}
|
||||
|
||||
return Optional.of(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class TestCases {
|
||||
abstract String testClass();
|
||||
|
||||
abstract ImmutableList<TestCase> testCases();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class TestCase {
|
||||
abstract String classUnderTest();
|
||||
|
||||
abstract ImmutableList<TestEntry> entries();
|
||||
}
|
||||
|
||||
interface TestEntry {
|
||||
String path();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class ReplacementTestEntry implements TestEntry {
|
||||
abstract String input();
|
||||
|
||||
abstract String output();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class IdentificationTestEntry implements TestEntry {
|
||||
abstract String code();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -46,7 +47,7 @@ public final class DocumentationGenerator implements Plugin {
|
||||
checkArgument(
|
||||
matcher.matches(), "'%s' must be of the form '%s=<value>'", pathArg, OUTPUT_DIRECTORY_FLAG);
|
||||
|
||||
String path = matcher.group(1);
|
||||
String path = requireNonNull(matcher.group(1), "Path must be present");
|
||||
try {
|
||||
return Path.of(path);
|
||||
} catch (InvalidPathException e) {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskEvent.Kind;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.api.JavacTrees;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.io.File;
|
||||
@@ -19,6 +24,7 @@ import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ServiceLoader;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
@@ -27,6 +33,13 @@ import javax.tools.JavaFileObject;
|
||||
*/
|
||||
// XXX: Find a better name for this class; it doesn't generate documentation per se.
|
||||
final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static final ImmutableList<Extractor<?>> EXTRACTORS =
|
||||
(ImmutableList)
|
||||
ImmutableList.copyOf(
|
||||
ServiceLoader.load(
|
||||
Extractor.class, DocumentationGeneratorTaskListener.class.getClassLoader()));
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER =
|
||||
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
|
||||
|
||||
@@ -51,19 +64,25 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
return;
|
||||
}
|
||||
|
||||
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
|
||||
JavaFileObject sourceFile = taskEvent.getSourceFile();
|
||||
if (classTree == null || sourceFile == null) {
|
||||
CompilationUnitTree compilationUnit = taskEvent.getCompilationUnit();
|
||||
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
|
||||
if (sourceFile == null || compilationUnit == null || classTree == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExtractorType.findMatchingType(classTree)
|
||||
.ifPresent(
|
||||
extractorType ->
|
||||
writeToFile(
|
||||
extractorType.getIdentifier(),
|
||||
getSimpleClassName(sourceFile.toUri()),
|
||||
extractorType.getExtractor().extract(classTree, context)));
|
||||
VisitorState state =
|
||||
VisitorState.createForUtilityPurposes(context)
|
||||
.withPath(new TreePath(new TreePath(compilationUnit), classTree));
|
||||
|
||||
for (Extractor<?> extractor : EXTRACTORS) {
|
||||
extractor
|
||||
.tryExtract(classTree, state)
|
||||
.ifPresent(
|
||||
data ->
|
||||
writeToFile(
|
||||
extractor.identifier(), getSimpleClassName(sourceFile.toUri()), data));
|
||||
}
|
||||
}
|
||||
|
||||
private void createDocsDirectory() {
|
||||
@@ -75,7 +94,7 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void writeToFile(String identifier, String className, T data) {
|
||||
private <T extends Object> void writeToFile(String identifier, String className, T data) {
|
||||
File file = docsPath.resolve(String.format("%s-%s.json", identifier, className)).toFile();
|
||||
|
||||
try (FileWriter fileWriter = new FileWriter(file, UTF_8)) {
|
||||
@@ -86,6 +105,8 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
}
|
||||
|
||||
private static String getSimpleClassName(URI path) {
|
||||
return Paths.get(path).getFileName().toString().replace(".java", "");
|
||||
return requireNonNull(Paths.get(path).getFileName(), "Path lacks filename")
|
||||
.toString()
|
||||
.replace(".java", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that define how to extract data of some type {@link T} from a
|
||||
@@ -13,21 +14,20 @@ import com.sun.tools.javac.util.Context;
|
||||
@Immutable
|
||||
interface Extractor<T> {
|
||||
/**
|
||||
* Extracts and returns an instance of {@link T} using the provided arguments.
|
||||
* Returns the unique identifier of this extractor.
|
||||
*
|
||||
* @param tree The {@link ClassTree} to analyze and from which to extract instances of {@link T}.
|
||||
* @param context The {@link Context} in which the current compilation takes place.
|
||||
* @return A non-null instance of {@link T}.
|
||||
* @return A non-{@code null} string.
|
||||
*/
|
||||
// XXX: Drop `Context` parameter unless used.
|
||||
T extract(ClassTree tree, Context context);
|
||||
String identifier();
|
||||
|
||||
/**
|
||||
* Tells whether this {@link Extractor} can extract documentation content from the given {@link
|
||||
* ClassTree}.
|
||||
* Attempts to extract an instance of type {@link T} using the provided arguments.
|
||||
*
|
||||
* @param tree The {@link ClassTree} of interest.
|
||||
* @return {@code true} iff data extraction is supported.
|
||||
* @param tree The {@link ClassTree} to analyze and from which to extract an instance of type
|
||||
* {@link T}.
|
||||
* @param state A {@link VisitorState} describing the context in which the given {@link ClassTree}
|
||||
* is found.
|
||||
* @return An instance of type {@link T}, if possible.
|
||||
*/
|
||||
boolean canExtract(ClassTree tree);
|
||||
Optional<T> tryExtract(ClassTree tree, VisitorState state);
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
/** An enumeration of {@link Extractor} types. */
|
||||
enum ExtractorType {
|
||||
BUG_PATTERN("bugpattern", new BugPatternExtractor());
|
||||
|
||||
private static final ImmutableSet<ExtractorType> TYPES =
|
||||
Sets.immutableEnumSet(EnumSet.allOf(ExtractorType.class));
|
||||
|
||||
private final String identifier;
|
||||
private final Extractor<?> extractor;
|
||||
|
||||
ExtractorType(String identifier, Extractor<?> extractor) {
|
||||
this.identifier = identifier;
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S1452" /* The extractor returns data of an unspecified type. */)
|
||||
Extractor<?> getExtractor() {
|
||||
return extractor;
|
||||
}
|
||||
|
||||
static Optional<ExtractorType> findMatchingType(ClassTree tree) {
|
||||
return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,9 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
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.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -45,10 +36,7 @@ final class BugPatternExtractorTest {
|
||||
"@BugPattern(summary = \"MinimalBugChecker summary\", severity = SeverityLevel.ERROR)",
|
||||
"public final class MinimalBugChecker extends BugChecker {}");
|
||||
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"bugpattern-MinimalBugChecker.json",
|
||||
"bugpattern-documentation-minimal.json");
|
||||
verifyGeneratedFileContent(outputDirectory, "MinimalBugChecker");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,10 +64,7 @@ final class BugPatternExtractorTest {
|
||||
" suppressionAnnotations = {BugPattern.class, Test.class})",
|
||||
"public final class CompleteBugChecker extends BugChecker {}");
|
||||
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"bugpattern-CompleteBugChecker.json",
|
||||
"bugpattern-documentation-complete.json");
|
||||
verifyGeneratedFileContent(outputDirectory, "CompleteBugChecker");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,55 +84,23 @@ final class BugPatternExtractorTest {
|
||||
" documentSuppression = false)",
|
||||
"public final class UndocumentedSuppressionBugPattern extends BugChecker {}");
|
||||
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"bugpattern-UndocumentedSuppressionBugPattern.json",
|
||||
"bugpattern-documentation-undocumented-suppression.json");
|
||||
verifyGeneratedFileContent(outputDirectory, "UndocumentedSuppressionBugPattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void bugPatternAnnotationIsAbsent() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"TestChecker.java",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: Can extract: false",
|
||||
"public final class TestChecker extends BugChecker {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
private static void verifyFileMatchesResource(
|
||||
Path outputDirectory, String fileName, String resourceName) throws IOException {
|
||||
assertThat(outputDirectory.resolve(fileName))
|
||||
private static void verifyGeneratedFileContent(Path outputDirectory, String testClass)
|
||||
throws IOException {
|
||||
String resourceName = String.format("bugpattern-%s.json", testClass);
|
||||
assertThat(outputDirectory.resolve(resourceName))
|
||||
.content(UTF_8)
|
||||
.isEqualToIgnoringWhitespace(getResource(resourceName));
|
||||
.isEqualToIgnoringWhitespace(
|
||||
getResource(
|
||||
String.join("-", BugPatternExtractorTest.class.getSimpleName(), resourceName)));
|
||||
}
|
||||
|
||||
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
|
||||
// text blocks in this class. (This also requires renaming the `verifyFileMatchesResource`
|
||||
// method.)
|
||||
// text blocks in this class.
|
||||
private static String getResource(String resourceName) throws IOException {
|
||||
return Resources.toString(
|
||||
Resources.getResource(BugPatternExtractorTest.class, resourceName), UTF_8);
|
||||
}
|
||||
|
||||
/** A {@link BugChecker} that validates the {@link BugPatternExtractor}. */
|
||||
@BugPattern(summary = "Validates `BugPatternExtractor` extraction", severity = ERROR)
|
||||
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
BugPatternExtractor extractor = new BugPatternExtractor();
|
||||
|
||||
assertThatThrownBy(() -> extractor.extract(tree, state.context))
|
||||
.isInstanceOf(NullPointerException.class)
|
||||
.hasMessage("BugPattern annotation must be present");
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage(String.format("Can extract: %s", extractor.canExtract(tree)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class BugPatternTestExtractorTest {
|
||||
@Test
|
||||
void noTestClass(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerWithoutAnnotation.java",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"public final class TestCheckerWithoutAnnotation extends BugChecker {}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noDoTestInvocation(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\");",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\");",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullBugCheckerInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance((Class<BugChecker>) null, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance((Class<BugChecker>) null, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rawBugCheckerInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" @SuppressWarnings(\"unchecked\")",
|
||||
" void m() {",
|
||||
" @SuppressWarnings(\"rawtypes\")",
|
||||
" Class bugChecker = TestChecker.class;",
|
||||
"",
|
||||
" CompilationTestHelper.newInstance(bugChecker, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(bugChecker, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scannerSupplierInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"import com.google.errorprone.scanner.ScannerSupplier;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(",
|
||||
" ScannerSupplier.fromBugCheckerClasses(TestChecker.class), getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(",
|
||||
" ScannerSupplier.fromBugCheckerClasses(TestChecker.class), getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonCompileTimeConstantStrings(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(toString() + \"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .addSourceLines(\"B.java\", \"// BUG: Diagnostic contains:\", \"class B {}\", toString())",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(toString() + \"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\", toString())",
|
||||
" .addOutputLines(\"B.java\", \"class B { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"C.java\", \"class C {}\")",
|
||||
" .addOutputLines(\"C.java\", \"class C { /* This is a change. */ }\", toString())",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonFluentTestHelperExpressions(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper testHelper =",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"class A {}\");",
|
||||
" testHelper.doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.ExpectOutput expectedOutput =",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\");",
|
||||
" expectedOutput.addOutputLines(\"A.java\", \"class A {}\").doTest();",
|
||||
" expectedOutput.expectUnchanged().doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSource(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass()).doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass()).doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noDiagnostics(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A {}\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\")",
|
||||
" .expectUnchanged()",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileCompilationTestHelper(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileCompilationTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(outputDirectory, "SingleFileCompilationTestHelperTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileCompilationTestHelperWithSetArgs(@TempDir Path outputDirectory)
|
||||
throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileCompilationTestHelperWithSetArgsTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .setArgs(\"-XepAllSuggestionsAsWarnings\")",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(outputDirectory, "SingleFileCompilationTestHelperWithSetArgsTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiFileCompilationTestHelper(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"MultiFileCompilationTestHelperTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class MultiFileCompilationTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .addSourceLines(\"B.java\", \"// BUG: Diagnostic contains:\", \"class B {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(outputDirectory, "MultiFileCompilationTestHelperTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileBugCheckerRefactoringTestHelper(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileBugCheckerRefactoringTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(outputDirectory, "SingleFileBugCheckerRefactoringTestHelperTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestMode(
|
||||
@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .setArgs(\"-XepAllSuggestionsAsWarnings\")",
|
||||
" .setFixChooser(FixChoosers.SECOND)",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest(TestMode.TEXT_MATCH);",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiFileBugCheckerRefactoringTestHelper(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class MultiFileBugCheckerRefactoringTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\")",
|
||||
" .addOutputLines(\"B.java\", \"class B { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(outputDirectory, "MultiFileBugCheckerRefactoringTestHelperTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compilationAndBugCheckerRefactoringTestHelpers(@TempDir Path outputDirectory)
|
||||
throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class CompilationAndBugCheckerRefactoringTestHelpersTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory, "CompilationAndBugCheckerRefactoringTestHelpersTest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNames(
|
||||
@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest {",
|
||||
" private static class CustomTestChecker extends BugChecker {}",
|
||||
"",
|
||||
" private static class CustomTestChecker2 extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(CustomTestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(CustomTestChecker2.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest");
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(Path outputDirectory, String testClass)
|
||||
throws IOException {
|
||||
String resourceName = String.format("bugpattern-test-%s.json", testClass);
|
||||
assertThat(outputDirectory.resolve(resourceName))
|
||||
.content(UTF_8)
|
||||
.isEqualToIgnoringWhitespace(
|
||||
getResource(
|
||||
String.join("-", BugPatternTestExtractorTest.class.getSimpleName(), resourceName)));
|
||||
}
|
||||
|
||||
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
|
||||
// text blocks in this class.
|
||||
private static String getResource(String resourceName) throws IOException {
|
||||
return Resources.toString(
|
||||
Resources.getResource(BugPatternTestExtractorTest.class, resourceName), UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,79 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.FileManagers;
|
||||
import com.google.errorprone.FileObjects;
|
||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
// XXX: Generalize and move this class so that it can also be used by `refaster-compiler`.
|
||||
// XXX: Add support for this class to the `ErrorProneTestHelperSourceFormat` check.
|
||||
// XXX: This class is supported by the `ErrorProneTestHelperSourceFormat` check, but until that
|
||||
// support is covered by unit tests, make sure to update that logic if this class or its methods are
|
||||
// moved/renamed.
|
||||
public final class Compilation {
|
||||
private Compilation() {}
|
||||
|
||||
public static void compileWithDocumentationGenerator(
|
||||
Path outputDirectory, String fileName, String... lines) {
|
||||
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), fileName, lines);
|
||||
Path outputDirectory, String path, String... lines) {
|
||||
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), path, lines);
|
||||
}
|
||||
|
||||
public static void compileWithDocumentationGenerator(
|
||||
String outputDirectory, String fileName, String... lines) {
|
||||
String outputDirectory, String path, String... lines) {
|
||||
/*
|
||||
* The compiler options specified here largely match those used by Error Prone's
|
||||
* `CompilationTestHelper`. A key difference is the stricter linting configuration. When
|
||||
* compiling using JDK 21+, these lint options also require that certain JDK modules are
|
||||
* explicitly exported.
|
||||
*/
|
||||
compile(
|
||||
ImmutableList.of("-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory),
|
||||
FileObjects.forSourceLines(fileName, lines));
|
||||
ImmutableList.of(
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"-encoding",
|
||||
"UTF-8",
|
||||
"-parameters",
|
||||
"-proc:none",
|
||||
"-Werror",
|
||||
"-Xlint:all,-serial",
|
||||
"-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory,
|
||||
"-XDdev",
|
||||
"-XDcompilePolicy=simple"),
|
||||
FileObjects.forSourceLines(path, lines));
|
||||
}
|
||||
|
||||
private static void compile(ImmutableList<String> options, JavaFileObject javaFileObject) {
|
||||
JavacFileManager javacFileManager = FileManagers.testFileManager();
|
||||
JavaCompiler compiler = JavacTool.create();
|
||||
JavacTaskImpl task =
|
||||
(JavacTaskImpl)
|
||||
compiler.getTask(
|
||||
null,
|
||||
javacFileManager,
|
||||
null,
|
||||
options,
|
||||
ImmutableList.of(),
|
||||
ImmutableList.of(javaFileObject));
|
||||
try (JavacFileManager javacFileManager = FileManagers.testFileManager()) {
|
||||
JavaCompiler compiler = JavacTool.create();
|
||||
|
||||
task.call();
|
||||
List<Diagnostic<?>> diagnostics = new ArrayList<>();
|
||||
JavacTaskImpl task =
|
||||
(JavacTaskImpl)
|
||||
compiler.getTask(
|
||||
null,
|
||||
javacFileManager,
|
||||
diagnostics::add,
|
||||
options,
|
||||
ImmutableList.of(),
|
||||
ImmutableList.of(javaFileObject));
|
||||
|
||||
Boolean result = task.call();
|
||||
assertThat(diagnostics).isEmpty();
|
||||
assertThat(result).isTrue();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to close `JavaCompiler`", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.attribute.AclEntryPermission.ADD_SUBDIRECTORY;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.condition.OS.WINDOWS;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.AclEntry;
|
||||
import java.nio.file.attribute.AclFileAttributeView;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
@@ -75,4 +85,56 @@ final class DocumentationGeneratorTaskListenerTest {
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Precisely one path must be provided");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extraction(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"DocumentationGeneratorTaskListenerTestClass.java",
|
||||
"class DocumentationGeneratorTaskListenerTestClass {}");
|
||||
|
||||
// XXX: Once we support only JDK 15+, use a text block for the `expected` string.
|
||||
assertThat(
|
||||
outputDirectory.resolve(
|
||||
"documentation-generator-task-listener-test-DocumentationGeneratorTaskListenerTestClass.json"))
|
||||
.content(UTF_8)
|
||||
.isEqualToIgnoringWhitespace(
|
||||
"{\"className\":\"DocumentationGeneratorTaskListenerTestClass\",\"path\":[\"CLASS: DocumentationGeneratorTaskListenerTestClass\",\"COMPILATION_UNIT\"]}");
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public static final class TestExtractor implements Extractor<ExtractionParameters> {
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "documentation-generator-task-listener-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ExtractionParameters> tryExtract(ClassTree tree, VisitorState state) {
|
||||
return Optional.of(tree.getSimpleName().toString())
|
||||
.filter(n -> n.contains(DocumentationGeneratorTaskListenerTest.class.getSimpleName()))
|
||||
.map(
|
||||
className ->
|
||||
new AutoValue_DocumentationGeneratorTaskListenerTest_ExtractionParameters(
|
||||
className,
|
||||
Streams.stream(state.getPath())
|
||||
.map(TestExtractor::describeTree)
|
||||
.collect(toImmutableList())));
|
||||
}
|
||||
|
||||
private static String describeTree(Tree tree) {
|
||||
return (tree instanceof ClassTree)
|
||||
? String.join(": ", String.valueOf(tree.getKind()), ((ClassTree) tree).getSimpleName())
|
||||
: tree.getKind().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class ExtractionParameters {
|
||||
abstract String className();
|
||||
|
||||
abstract ImmutableList<String> path();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"testClass": "CompilationAndBugCheckerRefactoringTestHelpersTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass A {}\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"classUnderTest": "CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"input": "class A {}\n",
|
||||
"output": "class A { /* This is a change. */ }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"testClass": "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass A {}\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"classUnderTest": "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker2",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"input": "class A {}\n",
|
||||
"output": "class A { /* This is a change. */ }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"testClass": "MultiFileBugCheckerRefactoringTestHelperTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "MultiFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"input": "class A {}\n",
|
||||
"output": "class A { /* This is a change. */ }\n"
|
||||
},
|
||||
{
|
||||
"path": "B.java",
|
||||
"input": "class B {}\n",
|
||||
"output": "class B { /* This is a change. */ }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"testClass": "MultiFileCompilationTestHelperTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "MultiFileCompilationTestHelperTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass A {}\n"
|
||||
},
|
||||
{
|
||||
"path": "B.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass B {}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"testClass": "SingleFileBugCheckerRefactoringTestHelperTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "SingleFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"input": "class A {}\n",
|
||||
"output": "class A { /* This is a change. */ }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"testClass": "SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"input": "class A {}\n",
|
||||
"output": "class A { /* This is a change. */ }\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"testClass": "SingleFileCompilationTestHelperTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "SingleFileCompilationTestHelperTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass A {}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"testClass": "SingleFileCompilationTestHelperWithSetArgsTest",
|
||||
"testCases": [
|
||||
{
|
||||
"classUnderTest": "SingleFileCompilationTestHelperWithSetArgsTest.TestChecker",
|
||||
"entries": [
|
||||
{
|
||||
"path": "A.java",
|
||||
"code": "// BUG: Diagnostic contains:\nclass A {}\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -125,9 +125,6 @@ The following is a list of checks we'd like to see implemented:
|
||||
statement. Idem for other exception types.
|
||||
- A Guava-specific check that replaces simple anonymous `CacheLoader` subclass
|
||||
declarations with `CacheLoader.from(someLambda)`.
|
||||
- A Spring-specific check that enforces that methods with the `@Scheduled`
|
||||
annotation are also annotated with New Relic's `@Trace` annotation. Such
|
||||
methods should ideally not also represent Spring MVC endpoints.
|
||||
- A Spring-specific check that enforces that `@RequestMapping` annotations,
|
||||
when applied to a method, explicitly specify one or more target HTTP methods.
|
||||
- A Spring-specific check that looks for classes in which all `@RequestMapping`
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.13.1-SNAPSHOT</version>
|
||||
<version>0.14.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -72,6 +72,11 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
@@ -81,11 +86,6 @@
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.newrelic.agent.java</groupId>
|
||||
<artifactId>newrelic-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
@@ -146,6 +146,10 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-util</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.immutables</groupId>
|
||||
<artifactId>value-annotations</artifactId>
|
||||
@@ -253,17 +257,6 @@
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<configuration>
|
||||
<ignoredUnusedDeclaredDependencies>
|
||||
<!-- XXX: Figure out why the plugin thinks this
|
||||
dependency is unused. -->
|
||||
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support</ignoredUnusedDeclaredDependency>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
@@ -9,7 +9,6 @@ import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -17,6 +16,7 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
@@ -48,11 +48,9 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableList<AnnotationTree> annotations =
|
||||
AUTOWIRED_ANNOTATION
|
||||
.multiMatchResult(Iterables.getOnlyElement(constructors), state)
|
||||
.matchingNodes();
|
||||
if (annotations.size() != 1) {
|
||||
MultiMatchResult<AnnotationTree> hasAutowiredAnnotation =
|
||||
AUTOWIRED_ANNOTATION.multiMatchResult(Iterables.getOnlyElement(constructors), state);
|
||||
if (!hasAutowiredAnnotation.matches()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -61,7 +59,7 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
|
||||
* means that the associated import can be removed as well. Rather than adding code for this
|
||||
* case we leave flagging the unused import to Error Prone's `RemoveUnusedImports` check.
|
||||
*/
|
||||
AnnotationTree annotation = Iterables.getOnlyElement(annotations);
|
||||
AnnotationTree annotation = hasAutowiredAnnotation.onlyMatchingNode();
|
||||
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
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.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anything;
|
||||
import static com.google.errorprone.matchers.Matchers.classLiteral;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.receiverOfInvocation;
|
||||
import static com.google.errorprone.matchers.Matchers.toType;
|
||||
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.annotations.Var;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.BinaryTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags invocations of {@link Class#getName()} where {@link
|
||||
* Class#getCanonicalName()} was likely meant.
|
||||
*
|
||||
* <p>For top-level types these two methods generally return the same result, but for nested types
|
||||
* the former separates identifiers using a dollar sign ({@code $}) rather than a dot ({@code .}).
|
||||
*
|
||||
* @implNote This check currently only flags {@link Class#getName()} invocations on class literals,
|
||||
* and doesn't flag method references. This avoids false positives, such as suggesting use of
|
||||
* {@link Class#getCanonicalName()} in contexts where the canonical name is {@code null}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "This code should likely use the type's canonical name",
|
||||
link = BUG_PATTERNS_BASE_URL + "CanonicalClassNameUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class CanonicalClassNameUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> GET_NAME_INVOCATION =
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
receiverOfInvocation(classLiteral(anything())),
|
||||
instanceMethod().onExactClass(Class.class.getCanonicalName()).named("getName")));
|
||||
private static final Pattern CANONICAL_NAME_USING_TYPES =
|
||||
Pattern.compile("(com\\.google\\.errorprone|tech\\.picnic\\.errorprone)\\..*");
|
||||
|
||||
/** Instantiates a new {@link CanonicalClassNameUsage} instance. */
|
||||
public CanonicalClassNameUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!GET_NAME_INVOCATION.matches(tree, state) || !isPassedToCanonicalNameUsingType(state)) {
|
||||
/*
|
||||
* This is not a `class.getName()` invocation of which the result is passed to another method
|
||||
* known to accept canonical type names.
|
||||
*/
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree, SuggestedFixes.renameMethodInvocation(tree, "getCanonicalName", state));
|
||||
}
|
||||
|
||||
private static boolean isPassedToCanonicalNameUsingType(VisitorState state) {
|
||||
@Var TreePath path = state.getPath().getParentPath();
|
||||
while (path.getLeaf() instanceof BinaryTree) {
|
||||
path = path.getParentPath();
|
||||
}
|
||||
|
||||
return path.getLeaf() instanceof MethodInvocationTree
|
||||
&& isOwnedByCanonicalNameUsingType(
|
||||
ASTHelpers.getSymbol((MethodInvocationTree) path.getLeaf()));
|
||||
}
|
||||
|
||||
private static boolean isOwnedByCanonicalNameUsingType(MethodSymbol symbol) {
|
||||
return CANONICAL_NAME_USING_TYPES.matcher(symbol.owner.getQualifiedName()).matches();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ 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.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -17,7 +20,11 @@ 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
@@ -38,7 +45,7 @@ import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
|
||||
staticMethod().onClass("java.util.stream.Collectors");
|
||||
staticMethod().onClass(Collectors.class.getCanonicalName());
|
||||
private static final Matcher<ExpressionTree> LIST_COLLECTOR =
|
||||
staticMethod().anyClass().named("toList");
|
||||
private static final Matcher<ExpressionTree> MAP_COLLECTOR =
|
||||
@@ -58,7 +65,10 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
if (LIST_COLLECTOR.matches(tree, state)) {
|
||||
return suggestToCollectionAlternatives(
|
||||
tree, "com.google.common.collect.ImmutableList.toImmutableList", "ArrayList", state);
|
||||
tree,
|
||||
ImmutableList.class.getCanonicalName() + ".toImmutableList",
|
||||
ArrayList.class.getCanonicalName(),
|
||||
state);
|
||||
}
|
||||
|
||||
if (MAP_COLLECTOR.matches(tree, state)) {
|
||||
@@ -67,7 +77,10 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
if (SET_COLLECTOR.matches(tree, state)) {
|
||||
return suggestToCollectionAlternatives(
|
||||
tree, "com.google.common.collect.ImmutableSet.toImmutableSet", "HashSet", state);
|
||||
tree,
|
||||
ImmutableSet.class.getCanonicalName() + ".toImmutableSet",
|
||||
HashSet.class.getCanonicalName(),
|
||||
state);
|
||||
}
|
||||
|
||||
return Description.NO_MATCH;
|
||||
@@ -75,20 +88,20 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
private Description suggestToCollectionAlternatives(
|
||||
MethodInvocationTree tree,
|
||||
String fullyQualifiedImmutableReplacement,
|
||||
String immutableReplacement,
|
||||
String mutableReplacement,
|
||||
VisitorState state) {
|
||||
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
|
||||
String toCollectionSelect =
|
||||
SuggestedFixes.qualifyStaticImport(
|
||||
"java.util.stream.Collectors.toCollection", mutableFix, state);
|
||||
Collectors.class.getCanonicalName() + ".toCollection", mutableFix, state);
|
||||
String mutableCollection = SuggestedFixes.qualifyType(state, mutableFix, mutableReplacement);
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(replaceMethodInvocation(tree, fullyQualifiedImmutableReplacement, state))
|
||||
.addFix(replaceMethodInvocation(tree, immutableReplacement, state))
|
||||
.addFix(
|
||||
mutableFix
|
||||
.addImport(String.format("java.util.%s", mutableReplacement))
|
||||
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableReplacement))
|
||||
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableCollection))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
@@ -99,17 +112,20 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
|
||||
String hashMap =
|
||||
SuggestedFixes.qualifyType(state, mutableFix, HashMap.class.getCanonicalName());
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(
|
||||
replaceMethodInvocation(
|
||||
tree, "com.google.common.collect.ImmutableMap.toImmutableMap", state))
|
||||
tree, ImmutableMap.class.getCanonicalName() + ".toImmutableMap", state))
|
||||
.addFix(
|
||||
SuggestedFix.builder()
|
||||
.addImport("java.util.HashMap")
|
||||
mutableFix
|
||||
.postfixWith(
|
||||
tree.getArguments().get(argCount - 1),
|
||||
(argCount == 2 ? ", (a, b) -> { throw new IllegalStateException(); }" : "")
|
||||
+ ", HashMap::new")
|
||||
+ String.format(", %s::new", hashMap))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
|
||||
private static final Matcher<StatementTree> VARIABLE_RETURN = returnStatement(isVariable());
|
||||
private static final Matcher<ExpressionTree> MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE =
|
||||
allOf(
|
||||
not(toType(MethodInvocationTree.class, argument(0, isSameType(Class.class.getName())))),
|
||||
not(
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
argument(0, isSameType(Class.class.getCanonicalName())))),
|
||||
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
|
||||
|
||||
/** Instantiates a new {@link DirectReturn} instance. */
|
||||
|
||||
@@ -36,7 +36,9 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
|
||||
private static final Matcher<Tree> PERMITTED_ANNOTATION =
|
||||
annotations(
|
||||
AT_LEAST_ONE,
|
||||
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
anyOf(
|
||||
isType(Override.class.getCanonicalName()),
|
||||
isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
|
||||
/** Instantiates a new {@link EmptyMethod} instance. */
|
||||
public EmptyMethod() {}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.LiteralTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
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.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import java.util.regex.Pattern;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags literal strings in Error Prone Support code that represent the
|
||||
* fully qualified class name of a type that's on Error Prone's classpath.
|
||||
*
|
||||
* <p>Deriving such strings from the associated {@link Class} instance makes for more maintainable
|
||||
* code.
|
||||
*/
|
||||
// XXX: Consider generalizing this to a `RuntimeClasspath` check with a configurable
|
||||
// `CLASSPATH_TYPES` regex. Such a check would likely be a no-op by default, with Error Prone
|
||||
// Support's parent specifying the regex that is currently hard-coded.
|
||||
// XXX: As-is this check may suggest usage of a string literal even if the relevant type will be on
|
||||
// the runtime classpath. Review how we can further reduce false-positives.
|
||||
// XXX: Slightly "out there", but we could try to derive the subset of classpath types likely to be
|
||||
// available at runtime from the `compile`-scoped Maven dependencies in the `pom.xml` file of
|
||||
// the module being compiled.
|
||||
// XXX: Right now this check does not rewrite primitive string references such as `"int"` to
|
||||
// `int.class.getCanonicalName()`. Review whether that's worth it.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"Prefer `Class#getCanonicalName()` over an equivalent string literal if and only if the "
|
||||
+ "type will be on the runtime classpath",
|
||||
link = BUG_PATTERNS_BASE_URL + "ErrorProneRuntimeClasspath",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class ErrorProneRuntimeClasspath extends BugChecker
|
||||
implements LiteralTreeMatcher, MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> GET_CANONICAL_NAME_INVOCATION =
|
||||
instanceMethod().onExactClass(Class.class.getCanonicalName()).named("getCanonicalName");
|
||||
private static final ImmutableSet<String> PRIMITIVE_TYPES =
|
||||
Primitives.allPrimitiveTypes().stream()
|
||||
.map(Class::getCanonicalName)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
/**
|
||||
* A pattern that matches fully qualified type names that are expected to be runtime dependencies
|
||||
* of Error Prone, and that are thus presumed to be unconditionally present on a bug checker's
|
||||
* runtime classpath.
|
||||
*/
|
||||
private static final Pattern CLASSPATH_TYPES =
|
||||
Pattern.compile(
|
||||
"com\\.google\\.common\\..*|com\\.google\\.errorprone\\.([^.]+(?<!TestHelper)(\\..*)?)|java\\..*");
|
||||
|
||||
/** Instantiates a new {@link ErrorProneRuntimeClasspath} instance. */
|
||||
public ErrorProneRuntimeClasspath() {}
|
||||
|
||||
@Override
|
||||
public Description matchLiteral(LiteralTree tree, VisitorState state) {
|
||||
String value = ASTHelpers.constValue(tree, String.class);
|
||||
if (value == null
|
||||
|| !CLASSPATH_TYPES.matcher(value).matches()
|
||||
|| state.findEnclosing(AnnotationTree.class) != null) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
SuggestedFix fix = trySuggestClassReference(tree, value, state);
|
||||
if (fix.isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage(
|
||||
"This type will be on the runtime classpath; use `Class#getCanonicalName()` instead")
|
||||
.addFix(fix)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!GET_CANONICAL_NAME_INVOCATION.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Symbol receiver = ASTHelpers.getSymbol(ASTHelpers.getReceiver(tree));
|
||||
if (receiver == null
|
||||
|| !(receiver.owner instanceof ClassSymbol)
|
||||
|| PRIMITIVE_TYPES.contains(receiver.owner.getQualifiedName().toString())
|
||||
|| CLASSPATH_TYPES.matcher(receiver.owner.getQualifiedName()).matches()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* This class reference may not be safe; suggest using a string literal instead. (Note that
|
||||
* dropping the type reference may make the associated import statement (if any) obsolete.
|
||||
* Dropping such imports is left to Error Prone's `RemoveUnusedImports` check.)
|
||||
*/
|
||||
return buildDescription(tree)
|
||||
.setMessage("This type may not be on the runtime classpath; use a string literal instead")
|
||||
.addFix(
|
||||
SuggestedFix.replace(
|
||||
tree, Constants.format(receiver.owner.getQualifiedName().toString())))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static SuggestedFix trySuggestClassReference(
|
||||
LiteralTree tree, String value, VisitorState state) {
|
||||
if (isTypeOnClasspath(value, state)) {
|
||||
return suggestClassReference(tree, value, "", state);
|
||||
}
|
||||
|
||||
int lastDot = value.lastIndexOf('.');
|
||||
String type = value.substring(0, lastDot);
|
||||
if (isTypeOnClasspath(type, state)) {
|
||||
return suggestClassReference(tree, type, value.substring(lastDot), state);
|
||||
}
|
||||
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
private static SuggestedFix suggestClassReference(
|
||||
LiteralTree original, String type, String suffix, VisitorState state) {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String identifier = SuggestedFixes.qualifyType(state, fix, type);
|
||||
return fix.replace(
|
||||
original,
|
||||
identifier
|
||||
+ ".class.getCanonicalName()"
|
||||
+ (suffix.isEmpty() ? "" : (" + " + Constants.format(suffix))))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isTypeOnClasspath(String type, VisitorState state) {
|
||||
try {
|
||||
return ThirdPartyLibrary.canIntroduceUsage(type, state);
|
||||
} catch (
|
||||
@SuppressWarnings("java:S1166" /* Not exceptional. */)
|
||||
IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
@@ -66,7 +67,12 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
.named("addSourceLines"),
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("addInputLines"));
|
||||
.named("addInputLines"),
|
||||
// XXX: Add tests for `Compilation.compileWithDocumentationGenerator`. Until done, make
|
||||
// sure to update this matcher if that method's class or name is changed/moved.
|
||||
staticMethod()
|
||||
.onClass("tech.picnic.errorprone.documentation.Compilation")
|
||||
.named("compileWithDocumentationGenerator"));
|
||||
private static final Matcher<ExpressionTree> OUTPUT_SOURCE_ACCEPTING_METHOD =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
@@ -83,7 +89,8 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
}
|
||||
|
||||
List<? extends ExpressionTree> sourceLines =
|
||||
tree.getArguments().subList(1, tree.getArguments().size());
|
||||
tree.getArguments()
|
||||
.subList(ASTHelpers.getSymbol(tree).params().size() - 1, tree.getArguments().size());
|
||||
if (sourceLines.isEmpty()) {
|
||||
return buildDescription(tree).setMessage("No source code provided").build();
|
||||
}
|
||||
@@ -149,12 +156,13 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
return FORMATTER.formatSource(withOptionallyRemovedImports);
|
||||
}
|
||||
|
||||
// XXX: This logic is duplicated in `BugPatternTestExtractor`. Can we do better?
|
||||
private static Optional<String> getConstantSourceCode(
|
||||
List<? extends ExpressionTree> sourceLines) {
|
||||
StringBuilder source = new StringBuilder();
|
||||
|
||||
for (ExpressionTree sourceLine : sourceLines) {
|
||||
Object value = ASTHelpers.constValue(sourceLine);
|
||||
String value = ASTHelpers.constValue(sourceLine, String.class);
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import java.util.stream.Stream;
|
||||
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
|
||||
staticMethod().onClass(Ordering.class.getName()).named("explicit");
|
||||
staticMethod().onClass(Ordering.class.getCanonicalName()).named("explicit");
|
||||
|
||||
/** Instantiates a new {@link ExplicitEnumOrdering} instance. */
|
||||
public ExplicitEnumOrdering() {}
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
instanceMethod()
|
||||
.onDescendantOf(FLUX)
|
||||
.namedAnyOf("flatMap", "flatMapSequential")
|
||||
.withParameters(Function.class.getName());
|
||||
.withParameters(Function.class.getCanonicalName());
|
||||
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
|
||||
VisitorState.memoize(
|
||||
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
|
||||
@@ -9,6 +9,7 @@ import static com.google.errorprone.matchers.method.MethodMatchers.instanceMetho
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -24,6 +25,7 @@ import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
@@ -48,7 +50,8 @@ public final class FluxImplicitBlock extends BugChecker implements MethodInvocat
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.namedAnyOf("toIterable", "toStream")
|
||||
.withNoParameters();
|
||||
private static final Supplier<Type> STREAM = Suppliers.typeFromString(Stream.class.getName());
|
||||
private static final Supplier<Type> STREAM =
|
||||
Suppliers.typeFromString(Stream.class.getCanonicalName());
|
||||
|
||||
/** Instantiates a new {@link FluxImplicitBlock} instance. */
|
||||
public FluxImplicitBlock() {}
|
||||
@@ -64,10 +67,11 @@ public final class FluxImplicitBlock extends BugChecker implements MethodInvocat
|
||||
if (ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)) {
|
||||
description.addFix(
|
||||
suggestBlockingElementCollection(
|
||||
tree, "com.google.common.collect.ImmutableList.toImmutableList", state));
|
||||
tree, ImmutableList.class.getCanonicalName() + ".toImmutableList", state));
|
||||
}
|
||||
description.addFix(
|
||||
suggestBlockingElementCollection(tree, "java.util.stream.Collectors.toList", state));
|
||||
suggestBlockingElementCollection(
|
||||
tree, Collectors.class.getCanonicalName() + ".toList", state));
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -29,7 +31,9 @@ import com.sun.source.tree.ParenthesizedTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.util.SimpleTreeVisitor;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -59,6 +63,7 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
public final class FormatStringConcatenation extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* AssertJ exposes varargs {@code fail} methods with a {@link Throwable}-accepting overload, the
|
||||
* latter of which should not be flagged.
|
||||
@@ -67,7 +72,8 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
anyMethod()
|
||||
.anyClass()
|
||||
.withAnyName()
|
||||
.withParameters(String.class.getName(), Throwable.class.getName());
|
||||
.withParameters(String.class.getCanonicalName(), Throwable.class.getCanonicalName());
|
||||
|
||||
// XXX: Drop some of these methods if we use Refaster to replace some with others.
|
||||
private static final Matcher<ExpressionTree> ASSERTJ_FORMAT_METHOD =
|
||||
anyOf(
|
||||
@@ -116,14 +122,14 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
private static final Matcher<ExpressionTree> GUAVA_FORMAT_METHOD =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass("com.google.common.base.Preconditions")
|
||||
.onClass(Preconditions.class.getCanonicalName())
|
||||
.namedAnyOf("checkArgument", "checkNotNull", "checkState"),
|
||||
staticMethod().onClass("com.google.common.base.Verify").named("verify"));
|
||||
staticMethod().onClass(Verify.class.getCanonicalName()).named("verify"));
|
||||
// XXX: Add `PrintWriter`, maybe others.
|
||||
private static final Matcher<ExpressionTree> JDK_FORMAT_METHOD =
|
||||
anyOf(
|
||||
staticMethod().onClass("java.lang.String").named("format"),
|
||||
instanceMethod().onExactClass("java.util.Formatter").named("format"));
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format"),
|
||||
instanceMethod().onExactClass(Formatter.class.getCanonicalName()).named("format"));
|
||||
private static final Matcher<ExpressionTree> SLF4J_FORMAT_METHOD =
|
||||
instanceMethod()
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
@@ -203,7 +209,9 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
}
|
||||
|
||||
private static boolean isStringTyped(ExpressionTree tree, VisitorState state) {
|
||||
return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state);
|
||||
// XXX: Open Error Prone PR to improve the `@Nullable` annotations on `ASTHelpers`.
|
||||
Type type = ASTHelpers.getType(tree);
|
||||
return type != null && ASTHelpers.isSameType(type, state.getSymtab().stringType, state);
|
||||
}
|
||||
|
||||
private static class ReplacementArgumentsConstructor
|
||||
|
||||
@@ -10,6 +10,17 @@ import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableRangeMap;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -20,6 +31,7 @@ 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.matchers.Matchers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.ASTHelpers.TargetType;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
@@ -37,6 +49,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
// the target method such a modification may change the code's semantics or performance.
|
||||
// XXX: Also flag `Stream#map`, `Mono#map` and `Flux#map` invocations where the given transformation
|
||||
// is effectively the identity operation.
|
||||
// XXX: Also flag nullary instance method invocations that represent an identity conversion, such as
|
||||
// `Boolean#booleanValue()`, `Byte#byteValue()` and friends.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid or clarify identity conversions",
|
||||
@@ -54,24 +68,22 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
|
||||
.map(Class::getName)
|
||||
.collect(toImmutableSet()))
|
||||
.named("valueOf"),
|
||||
staticMethod().onClass(String.class.getName()).named("valueOf"),
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("valueOf"),
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
"com.google.common.collect.ImmutableBiMap",
|
||||
"com.google.common.collect.ImmutableList",
|
||||
"com.google.common.collect.ImmutableListMultimap",
|
||||
"com.google.common.collect.ImmutableMap",
|
||||
"com.google.common.collect.ImmutableMultimap",
|
||||
"com.google.common.collect.ImmutableMultiset",
|
||||
"com.google.common.collect.ImmutableRangeMap",
|
||||
"com.google.common.collect.ImmutableRangeSet",
|
||||
"com.google.common.collect.ImmutableSet",
|
||||
"com.google.common.collect.ImmutableSetMultimap",
|
||||
"com.google.common.collect.ImmutableTable")
|
||||
ImmutableBiMap.class.getCanonicalName(),
|
||||
ImmutableList.class.getCanonicalName(),
|
||||
ImmutableListMultimap.class.getCanonicalName(),
|
||||
ImmutableMap.class.getCanonicalName(),
|
||||
ImmutableMultimap.class.getCanonicalName(),
|
||||
ImmutableMultiset.class.getCanonicalName(),
|
||||
ImmutableRangeMap.class.getCanonicalName(),
|
||||
ImmutableRangeSet.class.getCanonicalName(),
|
||||
ImmutableSet.class.getCanonicalName(),
|
||||
ImmutableSetMultimap.class.getCanonicalName(),
|
||||
ImmutableTable.class.getCanonicalName())
|
||||
.named("copyOf"),
|
||||
staticMethod()
|
||||
.onClass("com.google.errorprone.matchers.Matchers")
|
||||
.namedAnyOf("allOf", "anyOf"),
|
||||
staticMethod().onClass(Matchers.class.getCanonicalName()).namedAnyOf("allOf", "anyOf"),
|
||||
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
|
||||
staticMethod()
|
||||
.onClass("reactor.core.publisher.Flux")
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
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 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 tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
|
||||
|
||||
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.MethodTreeMatcher;
|
||||
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.matchers.MultiMatcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags nullary {@link
|
||||
* org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} test methods.
|
||||
*
|
||||
* <p>Such tests are unnecessarily executed more than necessary. This checker suggests annotating
|
||||
* the method with {@link org.junit.jupiter.api.Test @Test}, and to drop all declared {@link
|
||||
* org.junit.jupiter.params.provider.ArgumentsSource argument sources}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Nullary JUnit test methods should not be parameterized",
|
||||
link = BUG_PATTERNS_BASE_URL + "JUnitNullaryParameterizedTestDeclaration",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class JUnitNullaryParameterizedTestDeclaration extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<MethodTree, AnnotationTree> IS_PARAMETERIZED_TEST =
|
||||
annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.ParameterizedTest"));
|
||||
private static final Matcher<AnnotationTree> IS_ARGUMENT_SOURCE =
|
||||
anyOf(
|
||||
isType("org.junit.jupiter.params.provider.ArgumentsSource"),
|
||||
isType("org.junit.jupiter.params.provider.ArgumentsSources"),
|
||||
hasMetaAnnotation("org.junit.jupiter.params.provider.ArgumentsSource"));
|
||||
|
||||
/** Instantiates a new {@link JUnitNullaryParameterizedTestDeclaration} instance. */
|
||||
public JUnitNullaryParameterizedTestDeclaration() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!tree.getParameters().isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
MultiMatchResult<AnnotationTree> isParameterizedTest =
|
||||
IS_PARAMETERIZED_TEST.multiMatchResult(tree, state);
|
||||
if (!isParameterizedTest.matches()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is vacuously parameterized. Suggest replacing `@ParameterizedTest` with `@Test`.
|
||||
* (As each method is checked independently, we cannot in general determine whether this
|
||||
* suggestion makes a `ParameterizedTest` type import obsolete; that task is left to Error
|
||||
* Prone's `RemoveUnusedImports` check.)
|
||||
*/
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
fix.merge(
|
||||
SuggestedFix.replace(
|
||||
isParameterizedTest.onlyMatchingNode(),
|
||||
'@' + SuggestedFixes.qualifyType(state, fix, "org.junit.jupiter.api.Test")));
|
||||
|
||||
/*
|
||||
* Also suggest dropping all (explicit and implicit) `@ArgumentsSource`s. No attempt is made to
|
||||
* assess whether a dropped `@MethodSource` also makes the referenced factory method(s) unused.
|
||||
*/
|
||||
tree.getModifiers().getAnnotations().stream()
|
||||
.filter(a -> IS_ARGUMENT_SOURCE.matches(a, state))
|
||||
.forEach(a -> fix.merge(SourceCode.deleteWithTrailingWhitespace(a, state)));
|
||||
|
||||
return describeMatch(tree, fix.build());
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,15 @@ import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.HAS_METH
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.getMethodSourceFactoryNames;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
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.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
@@ -99,14 +102,14 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
|
||||
allOf(
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
Stream.class.getName(),
|
||||
IntStream.class.getName(),
|
||||
LongStream.class.getName(),
|
||||
DoubleStream.class.getName(),
|
||||
List.class.getName(),
|
||||
Set.class.getName(),
|
||||
"com.google.common.collect.ImmutableList",
|
||||
"com.google.common.collect.ImmutableSet")
|
||||
Stream.class.getCanonicalName(),
|
||||
IntStream.class.getCanonicalName(),
|
||||
LongStream.class.getCanonicalName(),
|
||||
DoubleStream.class.getCanonicalName(),
|
||||
List.class.getCanonicalName(),
|
||||
Set.class.getCanonicalName(),
|
||||
ImmutableList.class.getCanonicalName(),
|
||||
ImmutableSet.class.getCanonicalName())
|
||||
.named("of"),
|
||||
hasArguments(AT_LEAST_ONE, anything()),
|
||||
hasArguments(ALL, SUPPORTED_VALUE_FACTORY_VALUES)));
|
||||
@@ -168,6 +171,9 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
|
||||
return findMatchingSibling(tree, m -> m.getName().contentEquals(methodName), state);
|
||||
}
|
||||
|
||||
// XXX: File ticket for the `@SuppressWarnings`: the checker incorrectly claims that the predicate
|
||||
// only accepts `@InternedDistinct @SignatureBottom MethodTree` values (IIUC).
|
||||
@SuppressWarnings({"interning:argument", "signature:argument"})
|
||||
private static Optional<MethodTree> findMatchingSibling(
|
||||
MethodTree tree, Predicate<? super MethodTree> predicate, VisitorState state) {
|
||||
return state.findEnclosing(ClassTree.class).getMembers().stream()
|
||||
@@ -199,16 +205,21 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
|
||||
return getSingleReturnExpression(valueFactoryMethod)
|
||||
.flatMap(expression -> tryExtractValueSourceAttributeValue(expression, state))
|
||||
.map(
|
||||
valueSourceAttributeValue ->
|
||||
SuggestedFix.builder()
|
||||
.addImport("org.junit.jupiter.params.provider.ValueSource")
|
||||
.replace(
|
||||
methodSourceAnnotation,
|
||||
String.format(
|
||||
"@ValueSource(%s = %s)",
|
||||
toValueSourceAttributeName(parameterType), valueSourceAttributeValue))
|
||||
.delete(valueFactoryMethod)
|
||||
.build());
|
||||
valueSourceAttributeValue -> {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String valueSource =
|
||||
SuggestedFixes.qualifyType(
|
||||
state, fix, "org.junit.jupiter.params.provider.ValueSource");
|
||||
return fix.replace(
|
||||
methodSourceAnnotation,
|
||||
String.format(
|
||||
"@%s(%s = %s)",
|
||||
valueSource,
|
||||
toValueSourceAttributeName(parameterType),
|
||||
valueSourceAttributeValue))
|
||||
.delete(valueFactoryMethod)
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
// XXX: This pattern also occurs a few times inside Error Prone; contribute upstream.
|
||||
|
||||
@@ -76,6 +76,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListing:";
|
||||
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
|
||||
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
|
||||
|
||||
/**
|
||||
* The splitter applied to string-typed annotation arguments prior to lexicographical sorting. By
|
||||
* splitting on {@code =}, strings that represent e.g. inline Spring property declarations are
|
||||
@@ -219,7 +220,10 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
private static AnnotationAttributeMatcher createAnnotationAttributeMatcher(
|
||||
ErrorProneFlags flags) {
|
||||
return AnnotationAttributeMatcher.create(
|
||||
flags.getList(INCLUDED_ANNOTATIONS_FLAG), excludedAnnotations(flags));
|
||||
flags.get(INCLUDED_ANNOTATIONS_FLAG).isPresent()
|
||||
? Optional.of(flags.getListOrEmpty(INCLUDED_ANNOTATIONS_FLAG))
|
||||
: Optional.empty(),
|
||||
excludedAnnotations(flags));
|
||||
}
|
||||
|
||||
private static ImmutableList<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
|
||||
@@ -46,6 +46,7 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
public final class LexicographicalAnnotationListing extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* A comparator that minimally reorders {@link AnnotationType}s, such that declaration annotations
|
||||
* are placed before type annotations.
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.sun.tools.javac.code.Type;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Name;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
|
||||
@@ -189,13 +190,20 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.of(diff == 0 ? Optional.empty() : Optional.of(expectedArguments.get(0)));
|
||||
// XXX: These type hints shouldn't be necessary; see
|
||||
// https://github.com/typetools/checker-framework/issues/4007.
|
||||
return Optional.of(
|
||||
diff == 0
|
||||
? Optional.<@NonNull Name>empty()
|
||||
: Optional.<@NonNull Name>of(expectedArguments.get(0)));
|
||||
}
|
||||
|
||||
private static ImmutableList<Name> getVariables(LambdaExpressionTree tree) {
|
||||
return tree.getParameters().stream().map(VariableTree::getName).collect(toImmutableList());
|
||||
}
|
||||
|
||||
// XXX: Resolve this suppression.
|
||||
@SuppressWarnings("UnqualifiedSuggestedFixImport")
|
||||
private static Optional<SuggestedFix.Builder> constructFix(
|
||||
LambdaExpressionTree lambdaExpr, Symbol target, Object methodName) {
|
||||
Name sName = target.getSimpleName();
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class MockitoMockClassReference extends BugChecker
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE =
|
||||
allOf(
|
||||
argument(0, allOf(isSameType(Class.class.getName()), not(isVariable()))),
|
||||
argument(0, allOf(isSameType(Class.class.getCanonicalName()), not(isVariable()))),
|
||||
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
|
||||
|
||||
/** Instantiates a new {@link MockitoMockClassReference} instance. */
|
||||
@@ -71,7 +71,7 @@ public final class MockitoMockClassReference extends BugChecker
|
||||
Tree parent = state.getPath().getParentPath().getLeaf();
|
||||
switch (parent.getKind()) {
|
||||
case VARIABLE:
|
||||
return !ASTHelpers.hasNoExplicitType((VariableTree) parent, state)
|
||||
return !ASTHelpers.hasImplicitType((VariableTree) parent, state)
|
||||
&& MoreASTHelpers.areSameType(tree, parent, state);
|
||||
case ASSIGNMENT:
|
||||
return MoreASTHelpers.areSameType(tree, parent, state);
|
||||
|
||||
@@ -72,7 +72,7 @@ public final class NonEmptyMono extends BugChecker implements MethodInvocationTr
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.named("reduce")
|
||||
.withParameters(Object.class.getName(), BiFunction.class.getName()),
|
||||
.withParameters(Object.class.getCanonicalName(), BiFunction.class.getCanonicalName()),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
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 tech.picnic.errorprone.bugpatterns.StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags static imports of type members that should *not* be statically
|
||||
* imported.
|
||||
*/
|
||||
// XXX: This check is closely linked to `StaticImport`. Consider merging the two.
|
||||
// XXX: Add suppression support. If qualification of one more more identifiers is suppressed, then
|
||||
// the associated static import should *not* be removed.
|
||||
// XXX: Also introduce logic that disallows statically importing `ZoneOffset.ofHours` and other
|
||||
// `ofXXX`-style methods.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Member should not be statically imported",
|
||||
link = BUG_PATTERNS_BASE_URL + "NonStaticImport",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class NonStaticImport extends BugChecker implements CompilationUnitTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Types whose members should not be statically imported, unless exempted by {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
*
|
||||
* <p>Types listed here should be mutually exclusive with {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_TYPES}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> NON_STATIC_IMPORT_CANDIDATE_TYPES =
|
||||
ImmutableSet.of(
|
||||
ASTHelpers.class.getCanonicalName(),
|
||||
Clock.class.getCanonicalName(),
|
||||
Strings.class.getCanonicalName(),
|
||||
VisitorState.class.getCanonicalName(),
|
||||
ZoneOffset.class.getCanonicalName(),
|
||||
"com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode");
|
||||
|
||||
/**
|
||||
* Type members that should never be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Types listed by {@link #NON_STATIC_IMPORT_CANDIDATE_TYPES} and members listed by {@link
|
||||
* #NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS} should be omitted from this collection.
|
||||
* <li>This collection should be mutually exclusive with {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
* </ul>
|
||||
*/
|
||||
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
|
||||
// method name that could be considered "too vague" or could conceivably mean something else in a
|
||||
// specific context is left out.
|
||||
static final ImmutableSetMultimap<String, String> NON_STATIC_IMPORT_CANDIDATE_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.putAll(
|
||||
Collections.class.getCanonicalName(),
|
||||
"addAll",
|
||||
"copy",
|
||||
"fill",
|
||||
"list",
|
||||
"max",
|
||||
"min",
|
||||
"nCopies",
|
||||
"rotate",
|
||||
"sort",
|
||||
"swap")
|
||||
.put(Locale.class.getCanonicalName(), "ROOT")
|
||||
.putAll(Pattern.class.getCanonicalName(), "compile", "matches", "quote")
|
||||
.put(Predicates.class.getCanonicalName(), "contains")
|
||||
.put("org.springframework.http.MediaType", "ALL")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Identifiers that should never be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Identifiers listed by {@link StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS} should be
|
||||
* mutually exclusive with identifiers listed here.
|
||||
* <li>This list should contain a superset of the identifiers flagged by {@link
|
||||
* com.google.errorprone.bugpatterns.BadImport}.
|
||||
* </ul>
|
||||
*/
|
||||
static final ImmutableSet<String> NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS =
|
||||
ImmutableSet.of(
|
||||
"builder",
|
||||
"copyOf",
|
||||
"create",
|
||||
"empty",
|
||||
"from",
|
||||
"getDefaultInstance",
|
||||
"INSTANCE",
|
||||
"MAX",
|
||||
"MAX_VALUE",
|
||||
"MIN",
|
||||
"MIN_VALUE",
|
||||
"newBuilder",
|
||||
"newInstance",
|
||||
"of",
|
||||
"ONE",
|
||||
"parse",
|
||||
"valueOf",
|
||||
"ZERO");
|
||||
|
||||
/** Instantiates a new {@link NonStaticImport} instance. */
|
||||
public NonStaticImport() {}
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
ImmutableTable<String, String, UndesiredStaticImport> undesiredStaticImports =
|
||||
getUndesiredStaticImports(tree, state);
|
||||
|
||||
if (!undesiredStaticImports.isEmpty()) {
|
||||
replaceUndesiredStaticImportUsages(tree, undesiredStaticImports, state);
|
||||
|
||||
for (UndesiredStaticImport staticImport : undesiredStaticImports.values()) {
|
||||
state.reportMatch(
|
||||
describeMatch(staticImport.importTree(), staticImport.fixBuilder().build()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Any violations have been flagged against the offending static import statement. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static ImmutableTable<String, String, UndesiredStaticImport> getUndesiredStaticImports(
|
||||
CompilationUnitTree tree, VisitorState state) {
|
||||
ImmutableTable.Builder<String, String, UndesiredStaticImport> imports =
|
||||
ImmutableTable.builder();
|
||||
for (ImportTree importTree : tree.getImports()) {
|
||||
Tree qualifiedIdentifier = importTree.getQualifiedIdentifier();
|
||||
if (importTree.isStatic() && qualifiedIdentifier instanceof MemberSelectTree) {
|
||||
MemberSelectTree memberSelectTree = (MemberSelectTree) qualifiedIdentifier;
|
||||
String type = SourceCode.treeToString(memberSelectTree.getExpression(), state);
|
||||
String member = memberSelectTree.getIdentifier().toString();
|
||||
if (shouldNotBeStaticallyImported(type, member)) {
|
||||
imports.put(
|
||||
type,
|
||||
member,
|
||||
new AutoValue_NonStaticImport_UndesiredStaticImport(
|
||||
importTree, SuggestedFix.builder().removeStaticImport(type + '.' + member)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imports.build();
|
||||
}
|
||||
|
||||
private static boolean shouldNotBeStaticallyImported(String type, String member) {
|
||||
return (NON_STATIC_IMPORT_CANDIDATE_TYPES.contains(type)
|
||||
&& !STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member))
|
||||
|| NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member)
|
||||
|| NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(member);
|
||||
}
|
||||
|
||||
private static void replaceUndesiredStaticImportUsages(
|
||||
CompilationUnitTree tree,
|
||||
ImmutableTable<String, String, UndesiredStaticImport> undesiredStaticImports,
|
||||
VisitorState state) {
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
Symbol symbol = ASTHelpers.getSymbol(node);
|
||||
if (symbol != null) {
|
||||
UndesiredStaticImport staticImport =
|
||||
undesiredStaticImports.get(
|
||||
symbol.owner.getQualifiedName().toString(), symbol.name.toString());
|
||||
if (staticImport != null) {
|
||||
SuggestedFix.Builder fix = staticImport.fixBuilder();
|
||||
fix.prefixWith(node, SuggestedFixes.qualifyType(state, fix, symbol.owner) + '.');
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitIdentifier(node, unused);
|
||||
}
|
||||
}.scan(tree, null);
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class UndesiredStaticImport {
|
||||
abstract ImportTree importTree();
|
||||
|
||||
abstract SuggestedFix.Builder fixBuilder();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ 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.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
@@ -55,21 +56,21 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass(Comparator.class.getName())
|
||||
.onClass(Comparator.class.getCanonicalName())
|
||||
.namedAnyOf("comparingInt", "comparingLong", "comparingDouble"),
|
||||
staticMethod()
|
||||
.onClass(Comparator.class.getName())
|
||||
.onClass(Comparator.class.getCanonicalName())
|
||||
.named("comparing")
|
||||
.withParameters(Function.class.getName()));
|
||||
.withParameters(Function.class.getCanonicalName()));
|
||||
private static final Matcher<ExpressionTree> INSTANCE_COMPARISON_METHOD =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf(Comparator.class.getName())
|
||||
.onDescendantOf(Comparator.class.getCanonicalName())
|
||||
.namedAnyOf("thenComparingInt", "thenComparingLong", "thenComparingDouble"),
|
||||
instanceMethod()
|
||||
.onDescendantOf(Comparator.class.getName())
|
||||
.onDescendantOf(Comparator.class.getCanonicalName())
|
||||
.named("thenComparing")
|
||||
.withParameters(Function.class.getName()));
|
||||
.withParameters(Function.class.getCanonicalName()));
|
||||
|
||||
/** Instantiates a new {@link PrimitiveComparison} instance. */
|
||||
public PrimitiveComparison() {}
|
||||
@@ -167,10 +168,11 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
ExpressionTree expr = tree.getMethodSelect();
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
return SuggestedFix.builder()
|
||||
.addStaticImport(Comparator.class.getName() + '.' + preferredMethodName)
|
||||
.replace(expr, preferredMethodName)
|
||||
.build();
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String replacement =
|
||||
SuggestedFixes.qualifyStaticImport(
|
||||
Comparator.class.getCanonicalName() + '.' + preferredMethodName, fix, state);
|
||||
return fix.replace(expr, replacement).build();
|
||||
case MEMBER_SELECT:
|
||||
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
|
||||
return SuggestedFix.replace(
|
||||
|
||||
@@ -18,6 +18,8 @@ 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.Preconditions;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.Primitives;
|
||||
@@ -86,7 +88,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
private static final Matcher<MethodInvocationTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(Object.class.getName())
|
||||
.onDescendantOfAny(Object.class.getCanonicalName())
|
||||
.named("toString")
|
||||
.withNoParameters(),
|
||||
allOf(
|
||||
@@ -100,7 +102,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
.collect(toImmutableSet()))
|
||||
.named("toString"),
|
||||
allOf(
|
||||
staticMethod().onClass(String.class.getName()).named("valueOf"),
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("valueOf"),
|
||||
not(
|
||||
anyMethod()
|
||||
.anyClass()
|
||||
@@ -109,35 +111,37 @@ public final class RedundantStringConversion extends BugChecker
|
||||
ImmutableList.of(Suppliers.arrayOf(Suppliers.CHAR_TYPE))))))));
|
||||
private static final Matcher<ExpressionTree> STRINGBUILDER_APPEND_INVOCATION =
|
||||
instanceMethod()
|
||||
.onDescendantOf(StringBuilder.class.getName())
|
||||
.onDescendantOf(StringBuilder.class.getCanonicalName())
|
||||
.named("append")
|
||||
.withParameters(String.class.getName());
|
||||
.withParameters(String.class.getCanonicalName());
|
||||
private static final Matcher<ExpressionTree> STRINGBUILDER_INSERT_INVOCATION =
|
||||
instanceMethod()
|
||||
.onDescendantOf(StringBuilder.class.getName())
|
||||
.onDescendantOf(StringBuilder.class.getCanonicalName())
|
||||
.named("insert")
|
||||
.withParameters(int.class.getName(), String.class.getName());
|
||||
.withParameters(int.class.getCanonicalName(), String.class.getCanonicalName());
|
||||
private static final Matcher<ExpressionTree> FORMATTER_INVOCATION =
|
||||
anyOf(
|
||||
staticMethod().onClass(String.class.getName()).named("format"),
|
||||
instanceMethod().onDescendantOf(Formatter.class.getName()).named("format"),
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format"),
|
||||
instanceMethod().onDescendantOf(Formatter.class.getCanonicalName()).named("format"),
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.onDescendantOfAny(
|
||||
PrintStream.class.getCanonicalName(), PrintWriter.class.getCanonicalName())
|
||||
.namedAnyOf("format", "printf"),
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.onDescendantOfAny(
|
||||
PrintStream.class.getCanonicalName(), PrintWriter.class.getCanonicalName())
|
||||
.namedAnyOf("print", "println")
|
||||
.withParameters(Object.class.getName()),
|
||||
.withParameters(Object.class.getCanonicalName()),
|
||||
staticMethod()
|
||||
.onClass(Console.class.getName())
|
||||
.onClass(Console.class.getCanonicalName())
|
||||
.namedAnyOf("format", "printf", "readline", "readPassword"));
|
||||
private static final Matcher<ExpressionTree> GUAVA_GUARD_INVOCATION =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass("com.google.common.base.Preconditions")
|
||||
.onClass(Preconditions.class.getCanonicalName())
|
||||
.namedAnyOf("checkArgument", "checkState", "checkNotNull"),
|
||||
staticMethod()
|
||||
.onClass("com.google.common.base.Verify")
|
||||
.onClass(Verify.class.getCanonicalName())
|
||||
.namedAnyOf("verify", "verifyNotNull"));
|
||||
private static final Matcher<ExpressionTree> SLF4J_LOGGER_INVOCATION =
|
||||
instanceMethod()
|
||||
|
||||
@@ -35,7 +35,7 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
|
||||
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
|
||||
staticMethod().onClass(Refaster.class.getCanonicalName()).named("anyOf");
|
||||
|
||||
/** Instantiates a new {@link RefasterAnyOfUsage} instance. */
|
||||
public RefasterAnyOfUsage() {}
|
||||
|
||||
@@ -22,6 +22,10 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.io.InputStream;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@code @RequestMapping} methods that have one or more parameters
|
||||
@@ -70,10 +74,10 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestPart"))),
|
||||
isSameType("java.io.InputStream"),
|
||||
isSameType("java.time.ZoneId"),
|
||||
isSameType("java.util.Locale"),
|
||||
isSameType("java.util.TimeZone"),
|
||||
isSameType(InputStream.class.getCanonicalName()),
|
||||
isSameType(Locale.class.getCanonicalName()),
|
||||
isSameType(TimeZone.class.getCanonicalName()),
|
||||
isSameType(ZoneId.class.getCanonicalName()),
|
||||
isSameType("jakarta.servlet.http.HttpServletRequest"),
|
||||
isSameType("jakarta.servlet.http.HttpServletResponse"),
|
||||
isSameType("javax.servlet.http.HttpServletRequest"),
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
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.hasAnnotation;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.common.AnnotationMirrors;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
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.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
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.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
|
||||
* Relic Agent's {@code @Trace(dispatcher = true)}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Scheduled operation must start a new New Relic transaction",
|
||||
link = BUG_PATTERNS_BASE_URL + "ScheduledTransactionTrace",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class ScheduledTransactionTrace extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TRACE_ANNOTATION_FQCN = "com.newrelic.api.agent.Trace";
|
||||
private static final Matcher<Tree> IS_SCHEDULED =
|
||||
hasAnnotation("org.springframework.scheduling.annotation.Scheduled");
|
||||
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
|
||||
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
|
||||
|
||||
/** Instantiates a new {@link ScheduledTransactionTrace} instance. */
|
||||
public ScheduledTransactionTrace() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|
||||
|| !IS_SCHEDULED.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableList<AnnotationTree> traceAnnotations =
|
||||
TRACE_ANNOTATION.multiMatchResult(tree, state).matchingNodes();
|
||||
if (traceAnnotations.isEmpty()) {
|
||||
/* This method completely lacks the `@Trace` annotation; add it. */
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.builder()
|
||||
.addImport(TRACE_ANNOTATION_FQCN)
|
||||
.prefixWith(tree, "@Trace(dispatcher = true)")
|
||||
.build());
|
||||
}
|
||||
|
||||
AnnotationTree traceAnnotation = Iterables.getOnlyElement(traceAnnotations);
|
||||
if (isCorrectAnnotation(traceAnnotation)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `@Trace` annotation is present but does not specify `dispatcher = true`. Add or update
|
||||
* the `dispatcher` annotation element.
|
||||
*/
|
||||
return describeMatch(
|
||||
traceAnnotation,
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
traceAnnotation, state, "dispatcher", ImmutableList.of("true"))
|
||||
.build());
|
||||
}
|
||||
|
||||
private static boolean isCorrectAnnotation(AnnotationTree traceAnnotation) {
|
||||
return Boolean.TRUE.equals(
|
||||
AnnotationMirrors.getAnnotationValue(
|
||||
ASTHelpers.getAnnotationMirror(traceAnnotation), "dispatcher")
|
||||
.getValue());
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
@@ -114,9 +115,8 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
.map(arg -> SourceCode.treeToString(arg, state))
|
||||
.collect(joining(", "));
|
||||
|
||||
return SuggestedFix.builder()
|
||||
.addImport(ANN_PACKAGE_PREFIX + newAnnotation)
|
||||
.replace(tree, String.format("@%s(%s)", newAnnotation, newArguments))
|
||||
.build();
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String annotation = SuggestedFixes.qualifyType(state, fix, ANN_PACKAGE_PREFIX + newAnnotation);
|
||||
return fix.replace(tree, String.format("@%s(%s)", annotation, newArguments)).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,32 @@ 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 com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS;
|
||||
import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.Comparators;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.ImmutableSortedMultiset;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.collect.MoreCollectors;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -20,55 +38,73 @@ import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matchers;
|
||||
import com.google.errorprone.refaster.ImportPolicy;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags methods and constants that can and should be statically imported.
|
||||
*/
|
||||
/** A {@link BugChecker} that flags type members that can and should be statically imported. */
|
||||
// XXX: This check is closely linked to `NonStaticImport`. Consider merging the two.
|
||||
// XXX: Tricky cases:
|
||||
// - `org.springframework.http.HttpStatus` (not always an improvement, and `valueOf` must
|
||||
// certainly be excluded)
|
||||
// - `com.google.common.collect.Tables`
|
||||
// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN"}`
|
||||
// XXX: Also introduce a check that disallows static imports of certain methods. Candidates:
|
||||
// - `com.google.common.base.Strings`
|
||||
// - `java.util.Optional.empty`
|
||||
// - `java.util.Locale.ROOT`
|
||||
// - `ZoneOffset.ofHours` and other `ofXXX`-style methods.
|
||||
// - `java.time.Clock`.
|
||||
// - Several other `java.time` classes.
|
||||
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
|
||||
// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN}`
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Identifier should be statically imported",
|
||||
link = BUG_PATTERNS_BASE_URL + "StaticImport",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
tags = STYLE)
|
||||
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Types whose members should be statically imported, unless exempted by {@link
|
||||
* #STATIC_IMPORT_EXEMPTED_MEMBERS} or {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS}.
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS} or {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}.
|
||||
*
|
||||
* <p>Types listed here should be mutually exclusive with {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_TYPES}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_TYPES =
|
||||
ImmutableSet.of(
|
||||
"com.google.common.base.Preconditions",
|
||||
"com.google.common.base.Predicates",
|
||||
"com.google.common.base.Verify",
|
||||
"com.google.common.collect.MoreCollectors",
|
||||
"com.google.errorprone.BugPattern.LinkType",
|
||||
"com.google.errorprone.BugPattern.SeverityLevel",
|
||||
"com.google.errorprone.BugPattern.StandardTags",
|
||||
"com.google.errorprone.matchers.Matchers",
|
||||
"com.google.errorprone.refaster.ImportPolicy",
|
||||
BugPattern.LinkType.class.getCanonicalName(),
|
||||
BugPattern.SeverityLevel.class.getCanonicalName(),
|
||||
BugPattern.StandardTags.class.getCanonicalName(),
|
||||
Collections.class.getCanonicalName(),
|
||||
Collectors.class.getCanonicalName(),
|
||||
Comparator.class.getCanonicalName(),
|
||||
ImportPolicy.class.getCanonicalName(),
|
||||
Map.Entry.class.getCanonicalName(),
|
||||
Matchers.class.getCanonicalName(),
|
||||
MoreCollectors.class.getCanonicalName(),
|
||||
Pattern.class.getCanonicalName(),
|
||||
Preconditions.class.getCanonicalName(),
|
||||
Predicates.class.getCanonicalName(),
|
||||
StandardCharsets.class.getCanonicalName(),
|
||||
Verify.class.getCanonicalName(),
|
||||
"com.fasterxml.jackson.annotation.JsonCreator.Mode",
|
||||
"com.fasterxml.jackson.annotation.JsonFormat.Shape",
|
||||
"com.fasterxml.jackson.annotation.JsonInclude.Include",
|
||||
"com.fasterxml.jackson.annotation.JsonProperty.Access",
|
||||
"com.mongodb.client.model.Accumulators",
|
||||
"com.mongodb.client.model.Aggregates",
|
||||
"com.mongodb.client.model.Filters",
|
||||
@@ -76,12 +112,6 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"com.mongodb.client.model.Projections",
|
||||
"com.mongodb.client.model.Sorts",
|
||||
"com.mongodb.client.model.Updates",
|
||||
"java.nio.charset.StandardCharsets",
|
||||
"java.util.Collections",
|
||||
"java.util.Comparator",
|
||||
"java.util.Map.Entry",
|
||||
"java.util.regex.Pattern",
|
||||
"java.util.stream.Collectors",
|
||||
"org.assertj.core.api.Assertions",
|
||||
"org.assertj.core.api.InstanceOfAssertFactories",
|
||||
"org.assertj.core.api.SoftAssertions",
|
||||
@@ -104,93 +134,57 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"reactor.function.TupleUtils",
|
||||
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
|
||||
|
||||
/** Type members that should be statically imported. */
|
||||
@VisibleForTesting
|
||||
/**
|
||||
* Type members that should be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Types listed by {@link #STATIC_IMPORT_CANDIDATE_TYPES} should be omitted from this
|
||||
* collection.
|
||||
* <li>This collection should be mutually exclusive with {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
* <li>This collection should not list members contained in {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}.
|
||||
* </ul>
|
||||
*/
|
||||
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.putAll(Comparators.class.getCanonicalName(), "emptiesFirst", "emptiesLast")
|
||||
.put(Function.class.getCanonicalName(), "identity")
|
||||
.put(Functions.class.getCanonicalName(), "identity")
|
||||
.put(ImmutableList.class.getCanonicalName(), "toImmutableList")
|
||||
.putAll(
|
||||
"com.google.common.collect.ImmutableListMultimap",
|
||||
ImmutableListMultimap.class.getCanonicalName(),
|
||||
"flatteningToImmutableListMultimap",
|
||||
"toImmutableListMultimap")
|
||||
.put("com.google.common.collect.ImmutableList", "toImmutableList")
|
||||
.put("com.google.common.collect.ImmutableMap", "toImmutableMap")
|
||||
.put("com.google.common.collect.ImmutableMultiset", "toImmutableMultiset")
|
||||
.put("com.google.common.collect.ImmutableRangeSet", "toImmutableRangeSet")
|
||||
.put(ImmutableMap.class.getCanonicalName(), "toImmutableMap")
|
||||
.put(ImmutableMultiset.class.getCanonicalName(), "toImmutableMultiset")
|
||||
.put(ImmutableRangeSet.class.getCanonicalName(), "toImmutableRangeSet")
|
||||
.put(ImmutableSet.class.getCanonicalName(), "toImmutableSet")
|
||||
.putAll(
|
||||
"com.google.common.collect.ImmutableSetMultimap",
|
||||
ImmutableSetMultimap.class.getCanonicalName(),
|
||||
"flatteningToImmutableSetMultimap",
|
||||
"toImmutableSetMultimap")
|
||||
.put("com.google.common.collect.ImmutableSet", "toImmutableSet")
|
||||
.put("com.google.common.collect.ImmutableSortedMap", "toImmutableSortedMap")
|
||||
.put("com.google.common.collect.ImmutableSortedMultiset", "toImmutableSortedMultiset")
|
||||
.put("com.google.common.collect.ImmutableSortedSet", "toImmutableSortedSet")
|
||||
.put("com.google.common.collect.ImmutableTable", "toImmutableTable")
|
||||
.put("com.google.common.collect.Sets", "toImmutableEnumSet")
|
||||
.put("com.google.common.base.Functions", "identity")
|
||||
.put("java.time.ZoneOffset", "UTC")
|
||||
.put("java.util.function.Function", "identity")
|
||||
.put("java.util.function.Predicate", "not")
|
||||
.put("java.util.UUID", "randomUUID")
|
||||
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
|
||||
.put(ImmutableSortedMap.class.getCanonicalName(), "toImmutableSortedMap")
|
||||
.put(ImmutableSortedMultiset.class.getCanonicalName(), "toImmutableSortedMultiset")
|
||||
.put(ImmutableSortedSet.class.getCanonicalName(), "toImmutableSortedSet")
|
||||
.put(ImmutableTable.class.getCanonicalName(), "toImmutableTable")
|
||||
.putAll(
|
||||
"java.util.Objects",
|
||||
Objects.class.getCanonicalName(),
|
||||
"checkIndex",
|
||||
"checkFromIndexSize",
|
||||
"checkFromToIndex",
|
||||
"requireNonNull",
|
||||
"requireNonNullElse",
|
||||
"requireNonNullElseGet")
|
||||
.putAll("com.google.common.collect.Comparators", "emptiesFirst", "emptiesLast")
|
||||
.put(Predicate.class.getCanonicalName(), "not")
|
||||
.put(Sets.class.getCanonicalName(), "toImmutableEnumSet")
|
||||
.put(UUID.class.getCanonicalName(), "randomUUID")
|
||||
.put(ZoneOffset.class.getCanonicalName(), "UTC")
|
||||
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Type members that should never be statically imported.
|
||||
*
|
||||
* <p>Identifiers listed by {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS} should be omitted from
|
||||
* this collection.
|
||||
*/
|
||||
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
|
||||
// method name that could be considered "too vague" or could conceivably mean something else in a
|
||||
// specific context is left out.
|
||||
@VisibleForTesting
|
||||
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.put("com.mongodb.client.model.Filters", "empty")
|
||||
.putAll(
|
||||
"java.util.Collections",
|
||||
"addAll",
|
||||
"copy",
|
||||
"fill",
|
||||
"list",
|
||||
"max",
|
||||
"min",
|
||||
"nCopies",
|
||||
"rotate",
|
||||
"sort",
|
||||
"swap")
|
||||
.putAll("java.util.regex.Pattern", "compile", "matches", "quote")
|
||||
.put("org.springframework.http.MediaType", "ALL")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Identifiers that should never be statically imported.
|
||||
*
|
||||
* <p>This should be a superset of the identifiers flagged by {@link
|
||||
* com.google.errorprone.bugpatterns.BadImport}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> STATIC_IMPORT_EXEMPTED_IDENTIFIERS =
|
||||
ImmutableSet.of(
|
||||
"builder",
|
||||
"create",
|
||||
"copyOf",
|
||||
"from",
|
||||
"getDefaultInstance",
|
||||
"INSTANCE",
|
||||
"newBuilder",
|
||||
"of",
|
||||
"valueOf");
|
||||
|
||||
/** Instantiates a new {@link StaticImport} instance. */
|
||||
public StaticImport() {}
|
||||
|
||||
@@ -228,13 +222,13 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
|
||||
private static boolean isCandidate(MemberSelectTree tree) {
|
||||
String identifier = tree.getIdentifier().toString();
|
||||
if (STATIC_IMPORT_EXEMPTED_IDENTIFIERS.contains(identifier)) {
|
||||
if (NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Type type = ASTHelpers.getType(tree.getExpression());
|
||||
return type != null
|
||||
&& !STATIC_IMPORT_EXEMPTED_MEMBERS.containsEntry(type.toString(), identifier);
|
||||
&& !NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type.toString(), identifier);
|
||||
}
|
||||
|
||||
private static Optional<String> getCandidateSimpleName(StaticImportInfo importInfo) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.Convert;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import java.util.Formattable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -49,7 +49,7 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Splitter FORMAT_SPECIFIER_SPLITTER = Splitter.on("%s");
|
||||
private static final Matcher<ExpressionTree> STRING_FORMAT_INVOCATION =
|
||||
staticMethod().onClass(String.class.getName()).named("format");
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format");
|
||||
private static final Supplier<Type> CHAR_SEQUENCE_TYPE =
|
||||
Suppliers.typeFromClass(CharSequence.class);
|
||||
private static final Supplier<Type> FORMATTABLE_TYPE = Suppliers.typeFromClass(Formattable.class);
|
||||
@@ -150,7 +150,7 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(tree.getMethodSelect(), "String.join")
|
||||
.replace(arguments.next(), String.format("\"%s\"", Convert.quote(separator)));
|
||||
.replace(arguments.next(), Constants.format(separator));
|
||||
|
||||
while (arguments.hasNext()) {
|
||||
ExpressionTree argument = arguments.next();
|
||||
|
||||
@@ -45,11 +45,11 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
anyOf(
|
||||
allOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf(Clock.class.getName())
|
||||
.onDescendantOf(Clock.class.getCanonicalName())
|
||||
.namedAnyOf("getZone", "withZone"),
|
||||
not(enclosingClass(isSubtypeOf(Clock.class)))),
|
||||
staticMethod()
|
||||
.onClass(Clock.class.getName())
|
||||
.onClass(Clock.class.getCanonicalName())
|
||||
.namedAnyOf(
|
||||
"system",
|
||||
"systemDefaultZone",
|
||||
@@ -59,14 +59,17 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
"tickSeconds"),
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
LocalDate.class.getName(),
|
||||
LocalDateTime.class.getName(),
|
||||
LocalTime.class.getName(),
|
||||
OffsetDateTime.class.getName(),
|
||||
OffsetTime.class.getName(),
|
||||
ZonedDateTime.class.getName())
|
||||
LocalDate.class.getCanonicalName(),
|
||||
LocalDateTime.class.getCanonicalName(),
|
||||
LocalTime.class.getCanonicalName(),
|
||||
OffsetDateTime.class.getCanonicalName(),
|
||||
OffsetTime.class.getCanonicalName(),
|
||||
ZonedDateTime.class.getCanonicalName())
|
||||
.named("now"),
|
||||
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
|
||||
staticMethod()
|
||||
.onClassAny(Instant.class.getCanonicalName())
|
||||
.named("now")
|
||||
.withNoParameters());
|
||||
|
||||
/** Instantiates a new {@link TimeZoneUsage} instance. */
|
||||
public TimeZoneUsage() {}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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.Matchers.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.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags suggested fixes that involve unconditional imports.
|
||||
*
|
||||
* <p>Such unconditional imports may clash with other imports of the same identifier.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid direct invocation of `SuggestedFix#add{,Static}Import`",
|
||||
link = BUG_PATTERNS_BASE_URL + "UnqualifiedSuggestedFixImport",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class UnqualifiedSuggestedFixImport extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> FIX_BUILDER_METHOD =
|
||||
instanceMethod().onDescendantOf(SuggestedFix.Builder.class.getCanonicalName());
|
||||
|
||||
/** Instantiates a new {@link UnqualifiedSuggestedFixImport} instance. */
|
||||
public UnqualifiedSuggestedFixImport() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!FIX_BUILDER_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
switch (ASTHelpers.getSymbol(tree).getSimpleName().toString()) {
|
||||
case "addImport":
|
||||
return createDescription(
|
||||
tree, "SuggestedFix.Builder#addImport", "SuggestedFixes#qualifyType");
|
||||
case "addStaticImport":
|
||||
return createDescription(
|
||||
tree, "SuggestedFix.Builder#addStaticImport", "SuggestedFixes#qualifyStaticImport");
|
||||
default:
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
private Description createDescription(
|
||||
MethodInvocationTree tree, String method, String alternative) {
|
||||
return buildDescription(tree)
|
||||
.setMessage(
|
||||
String.format("Prefer `%s` over direct invocation of `%s`", alternative, method))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,7 @@ public final class Flags {
|
||||
* provided, or if the flag's value is the empty string.
|
||||
*/
|
||||
public static ImmutableList<String> getList(ErrorProneFlags errorProneFlags, String name) {
|
||||
return errorProneFlags
|
||||
.getList(name)
|
||||
.map(ImmutableList::copyOf)
|
||||
.filter(flags -> !flags.equals(ImmutableList.of("")))
|
||||
.orElseGet(ImmutableList::of);
|
||||
ImmutableList<String> list = errorProneFlags.getListOrEmpty(name);
|
||||
return list.equals(ImmutableList.of("")) ? ImmutableList.of() : list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ public final class JavaKeywords {
|
||||
*/
|
||||
private static final ImmutableSet<String> BOOLEAN_AND_NULL_LITERALS =
|
||||
ImmutableSet.of("true", "false", "null");
|
||||
|
||||
/**
|
||||
* List of all reserved keywords in the Java language.
|
||||
*
|
||||
@@ -76,6 +77,7 @@ public final class JavaKeywords {
|
||||
"void",
|
||||
"volatile",
|
||||
"while");
|
||||
|
||||
/**
|
||||
* List of all contextual keywords in the Java language.
|
||||
*
|
||||
@@ -100,6 +102,7 @@ public final class JavaKeywords {
|
||||
"var",
|
||||
"with",
|
||||
"yield");
|
||||
|
||||
/** List of all keywords in the Java language. */
|
||||
private static final ImmutableSet<String> ALL_KEYWORDS =
|
||||
Sets.union(RESERVED_KEYWORDS, CONTEXTUAL_KEYWORDS).immutableCopy();
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.util.regex.Pattern;
|
||||
public final class MethodMatcherFactory {
|
||||
private static final Splitter ARGUMENT_TYPE_SPLITTER =
|
||||
Splitter.on(',').trimResults().omitEmptyStrings();
|
||||
|
||||
// XXX: Check whether we can use a parser for "standard" Java signatures here. Maybe
|
||||
// `sun.reflect.generics.parser.SignatureParser`?
|
||||
@SuppressWarnings("java:S5998" /* In practice there will be only modest recursion. */)
|
||||
@@ -44,6 +45,9 @@ public final class MethodMatcherFactory {
|
||||
// XXX: It seems parse errors are silently swallowed. Double-check; if true, file a ticket.
|
||||
// XXX: This (probably) doesn't work for methods with array type arguments; if true, implement a
|
||||
// fix.
|
||||
// XXX: The `nullness` warning suppression shouldn't be necessary. See
|
||||
// https://github.com/typetools/checker-framework/issues/4006.
|
||||
@SuppressWarnings("nullness:argument")
|
||||
private static Matcher<ExpressionTree> createMethodMatcher(CharSequence signature) {
|
||||
java.util.regex.Matcher m = METHOD_SIGNATURE.matcher(signature);
|
||||
checkArgument(m.matches(), "Not a valid method signature: %s", signature);
|
||||
|
||||
@@ -34,6 +34,7 @@ public final class MoreJUnitMatchers {
|
||||
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(
|
||||
@@ -43,6 +44,7 @@ public final class MoreJUnitMatchers {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
@@ -31,14 +32,7 @@ public enum ThirdPartyLibrary {
|
||||
*
|
||||
* @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"),
|
||||
GUAVA(ImmutableList.class.getCanonicalName()),
|
||||
/**
|
||||
* VMWare's Project Reactor.
|
||||
*
|
||||
@@ -73,7 +67,14 @@ public enum ThirdPartyLibrary {
|
||||
return canUse.get(state);
|
||||
}
|
||||
|
||||
private static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
/**
|
||||
* Tells whether the given fully qualified type is available on the current class path.
|
||||
*
|
||||
* @param className The type of interest.
|
||||
* @param state The context under consideration.
|
||||
* @return {@code true} iff it is okay to assume or create a dependency on this type.
|
||||
*/
|
||||
public static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,80 +3,40 @@ 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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.AbstractCollectionAssert;
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import org.assertj.core.api.MapAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
|
||||
|
||||
@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) {
|
||||
void before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert,
|
||||
@Matches(IsEmpty.class) Map<? extends K, ? extends V> wellTypedMap,
|
||||
@Matches(IsEmpty.class) Map<?, ?> arbitrarilyTypedMap,
|
||||
@Matches(IsEmpty.class) Iterable<? extends K> keys) {
|
||||
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.containsExactlyEntriesOf(wellTypedMap),
|
||||
mapAssert.containsExactlyInAnyOrderEntriesOf(wellTypedMap),
|
||||
mapAssert.hasSameSizeAs(arbitrarilyTypedMap),
|
||||
mapAssert.isEqualTo(arbitrarilyTypedMap),
|
||||
mapAssert.containsOnlyKeys(keys),
|
||||
mapAssert.containsExactly(),
|
||||
mapAssert.containsOnly(),
|
||||
mapAssert.containsOnlyKeys());
|
||||
@@ -112,15 +72,9 @@ final class AssertJMapRules {
|
||||
|
||||
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<>()));
|
||||
AbstractMapAssert<?, ?, K, V> before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, @Matches(IsEmpty.class) Map<?, ?> map) {
|
||||
return mapAssert.isNotEqualTo(map);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -148,12 +102,14 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
|
||||
return mapAssert.isEqualTo(map);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> after(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
|
||||
return mapAssert.containsExactlyInAnyOrderEntriesOf(map);
|
||||
}
|
||||
}
|
||||
@@ -187,12 +143,12 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AbstractMapAssertHasSameSizeAs<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
|
||||
return mapAssert.hasSize(map.size());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
|
||||
return mapAssert.hasSameSizeAs(map);
|
||||
}
|
||||
}
|
||||
@@ -225,13 +181,14 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AssertThatMapContainsOnlyKeys<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(Map<K, V> map, Set<K> keys) {
|
||||
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(
|
||||
Map<K, V> map, Set<? extends K> keys) {
|
||||
return assertThat(map.keySet()).hasSameElementsAs(keys);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
MapAssert<K, V> after(Map<K, V> map, Set<K> keys) {
|
||||
MapAssert<K, V> after(Map<K, V> map, Set<? extends K> keys) {
|
||||
return assertThat(map).containsOnlyKeys(keys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,28 +6,23 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMultiset;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Multiset;
|
||||
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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.NotMatches;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
@@ -47,6 +42,7 @@ import org.assertj.core.api.OptionalIntAssert;
|
||||
import org.assertj.core.api.OptionalLongAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsArray;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
|
||||
|
||||
/** Refaster rules related to AssertJ expressions and statements. */
|
||||
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
|
||||
@@ -181,63 +177,16 @@ final class AssertJRules {
|
||||
static final class AssertThatObjectEnumerableIsEmpty<E> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
void before(ObjectEnumerableAssert<?, E> enumAssert) {
|
||||
void before(
|
||||
ObjectEnumerableAssert<?, E> enumAssert,
|
||||
@Matches(IsEmpty.class) Iterable<? extends E> wellTypedIterable,
|
||||
@Matches(IsEmpty.class) Iterable<?> arbitrarilyTypedIterable) {
|
||||
Refaster.anyOf(
|
||||
enumAssert.containsExactlyElementsOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.containsExactlyInAnyOrderElementsOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.hasSameElementsAs(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.hasSameSizeAs(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.isSubsetOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.containsExactlyElementsOf(wellTypedIterable),
|
||||
enumAssert.containsExactlyInAnyOrderElementsOf(wellTypedIterable),
|
||||
enumAssert.hasSameElementsAs(wellTypedIterable),
|
||||
enumAssert.hasSameSizeAs(arbitrarilyTypedIterable),
|
||||
enumAssert.isSubsetOf(wellTypedIterable),
|
||||
enumAssert.containsExactly(),
|
||||
enumAssert.containsExactlyInAnyOrder(),
|
||||
enumAssert.containsOnly(),
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.checkerframework.checker.regex.qual.Regex;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@OnlineDocumentation
|
||||
@@ -71,26 +72,26 @@ final class AssertJStringRules {
|
||||
|
||||
static final class AssertThatMatches {
|
||||
@BeforeTemplate
|
||||
AbstractAssert<?, ?> before(String string, String regex) {
|
||||
AbstractAssert<?, ?> before(String string, @Regex String regex) {
|
||||
return assertThat(string.matches(regex)).isTrue();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractAssert<?, ?> after(String string, String regex) {
|
||||
AbstractAssert<?, ?> after(String string, @Regex String regex) {
|
||||
return assertThat(string).matches(regex);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatDoesNotMatch {
|
||||
@BeforeTemplate
|
||||
AbstractAssert<?, ?> before(String string, String regex) {
|
||||
AbstractAssert<?, ?> before(String string, @Regex String regex) {
|
||||
return assertThat(string.matches(regex)).isFalse();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractAssert<?, ?> after(String string, String regex) {
|
||||
AbstractAssert<?, ?> after(String string, @Regex String regex) {
|
||||
return assertThat(string).doesNotMatch(regex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.io.IOException;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.AbstractThrowableAssert;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.checkerframework.checker.formatter.qual.FormatMethod;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
@@ -591,6 +592,7 @@ final class AssertJThrowingCallableRules {
|
||||
// arguments to a wide range of format methods.
|
||||
static final class AbstractThrowableAssertHasMessage {
|
||||
@BeforeTemplate
|
||||
@FormatMethod
|
||||
AbstractThrowableAssert<?, ? extends Throwable> before(
|
||||
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
|
||||
String message,
|
||||
@@ -599,6 +601,7 @@ final class AssertJThrowingCallableRules {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@FormatMethod
|
||||
AbstractThrowableAssert<?, ? extends Throwable> after(
|
||||
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
|
||||
String message,
|
||||
@@ -611,6 +614,7 @@ final class AssertJThrowingCallableRules {
|
||||
// arguments to a wide range of format methods.
|
||||
static final class AbstractThrowableAssertWithFailMessage {
|
||||
@BeforeTemplate
|
||||
@FormatMethod
|
||||
AbstractThrowableAssert<?, ? extends Throwable> before(
|
||||
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
|
||||
String message,
|
||||
@@ -619,6 +623,7 @@ final class AssertJThrowingCallableRules {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@FormatMethod
|
||||
AbstractThrowableAssert<?, ? extends Throwable> after(
|
||||
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
|
||||
String message,
|
||||
|
||||
@@ -103,8 +103,7 @@ final class AssortedRules {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
T after(Iterator<T> iterator, T defaultValue) {
|
||||
@Nullable T after(Iterator<T> iterator, T defaultValue) {
|
||||
return Iterators.getNext(iterator, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.AlsoNegation;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.math.BigDecimal;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
@@ -66,4 +67,61 @@ final class BigDecimalRules {
|
||||
return BigDecimal.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link BigDecimal#signum()} over more contrived alternatives. */
|
||||
static final class BigDecimalSignumIsZero {
|
||||
@BeforeTemplate
|
||||
boolean before(BigDecimal value) {
|
||||
return Refaster.anyOf(
|
||||
value.compareTo(BigDecimal.ZERO) == 0, BigDecimal.ZERO.compareTo(value) == 0);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(BigDecimal value) {
|
||||
return value.signum() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer a {@link BigDecimal#signum()} comparison to 1 over more contrived or less clear
|
||||
* alternatives.
|
||||
*/
|
||||
static final class BigDecimalSignumIsPositive {
|
||||
@BeforeTemplate
|
||||
boolean before(BigDecimal value) {
|
||||
return Refaster.anyOf(
|
||||
value.compareTo(BigDecimal.ZERO) > 0,
|
||||
BigDecimal.ZERO.compareTo(value) < 0,
|
||||
value.signum() > 0,
|
||||
value.signum() >= 1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(BigDecimal value) {
|
||||
return value.signum() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer a {@link BigDecimal#signum()} comparison to -1 over more contrived or less clear
|
||||
* alternatives.
|
||||
*/
|
||||
static final class BigDecimalSignumIsNegative {
|
||||
@BeforeTemplate
|
||||
boolean before(BigDecimal value) {
|
||||
return Refaster.anyOf(
|
||||
value.compareTo(BigDecimal.ZERO) < 0,
|
||||
BigDecimal.ZERO.compareTo(value) > 0,
|
||||
value.signum() < 0,
|
||||
value.signum() <= -1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(BigDecimal value) {
|
||||
return value.signum() == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import com.sun.tools.javac.util.Convert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to {@link com.google.errorprone.bugpatterns.BugChecker} classes. */
|
||||
@@ -52,4 +54,17 @@ final class BugCheckerRules {
|
||||
return helper.addInputLines(path, source).expectUnchanged();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using the {@link Constants} API over more verbose alternatives. */
|
||||
static final class ConstantsFormat {
|
||||
@BeforeTemplate
|
||||
String before(String value) {
|
||||
return String.format("\"%s\"", Convert.quote(value));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(String value) {
|
||||
return Constants.format(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
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.function.Predicate;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with classes. */
|
||||
@OnlineDocumentation
|
||||
final class ClassRules {
|
||||
private ClassRules() {}
|
||||
|
||||
/** Prefer {@link Class#isInstance(Object)} over more contrived alternatives. */
|
||||
static final class ClassIsInstance<T, S> {
|
||||
@BeforeTemplate
|
||||
boolean before(Class<T> clazz, S object) {
|
||||
return clazz.isAssignableFrom(object.getClass());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(Class<T> clazz, S object) {
|
||||
return clazz.isInstance(object);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using the {@code instanceof} keyword over less idiomatic alternatives. */
|
||||
static final class Instanceof<T, S> {
|
||||
@BeforeTemplate
|
||||
boolean before(S object) {
|
||||
return Refaster.<T>clazz().isInstance(object);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(S object) {
|
||||
return Refaster.<T>isInstance(object);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
|
||||
static final class ClassLiteralIsInstancePredicate<T, S> {
|
||||
@BeforeTemplate
|
||||
Predicate<S> before() {
|
||||
return o -> Refaster.<T>isInstance(o);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Predicate<S> after() {
|
||||
return Refaster.<T>clazz()::isInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
|
||||
static final class ClassReferenceIsInstancePredicate<T, S> {
|
||||
@BeforeTemplate
|
||||
Predicate<S> before(Class<T> clazz) {
|
||||
return o -> clazz.isInstance(o);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Predicate<S> after(Class<T> clazz) {
|
||||
return clazz::isInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import static java.util.Comparator.comparingInt;
|
||||
import static java.util.Comparator.comparingLong;
|
||||
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;
|
||||
@@ -16,6 +15,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Arrays;
|
||||
@@ -28,6 +28,7 @@ import java.util.function.ToIntFunction;
|
||||
import java.util.function.ToLongFunction;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link Comparator}s. */
|
||||
@OnlineDocumentation
|
||||
@@ -36,12 +37,12 @@ final class ComparatorRules {
|
||||
|
||||
/** Prefer {@link Comparator#naturalOrder()} over more complicated constructs. */
|
||||
static final class NaturalOrder<T extends Comparable<? super T>> {
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
@BeforeTemplate
|
||||
Comparator<T> before() {
|
||||
Comparator<T> before(
|
||||
@Matches(IsIdentityOperation.class) Function<? super T, ? extends T> keyExtractor) {
|
||||
return Refaster.anyOf(
|
||||
T::compareTo,
|
||||
comparing(Refaster.anyOf(identity(), v -> v)),
|
||||
comparing(keyExtractor),
|
||||
Collections.<T>reverseOrder(reverseOrder()),
|
||||
Comparator.<T>reverseOrder().reversed());
|
||||
}
|
||||
@@ -72,10 +73,11 @@ final class ComparatorRules {
|
||||
}
|
||||
|
||||
static final class CustomComparator<T> {
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
@BeforeTemplate
|
||||
Comparator<T> before(Comparator<T> cmp) {
|
||||
return comparing(Refaster.anyOf(identity(), v -> v), cmp);
|
||||
Comparator<T> before(
|
||||
Comparator<T> cmp,
|
||||
@Matches(IsIdentityOperation.class) Function<? super T, ? extends T> keyExtractor) {
|
||||
return comparing(keyExtractor, cmp);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -183,14 +185,15 @@ final class ComparatorRules {
|
||||
}
|
||||
|
||||
/**
|
||||
* Where applicable, prefer {@link Comparator#naturalOrder()} over {@link Function#identity()}, as
|
||||
* it more clearly states intent.
|
||||
* Where applicable, prefer {@link Comparator#naturalOrder()} over identity function-based
|
||||
* comparisons, as the former more clearly states intent.
|
||||
*/
|
||||
static final class ThenComparingNaturalOrder<T extends Comparable<? super T>> {
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
@BeforeTemplate
|
||||
Comparator<T> before(Comparator<T> cmp) {
|
||||
return cmp.thenComparing(Refaster.anyOf(identity(), v -> v));
|
||||
Comparator<T> before(
|
||||
Comparator<T> cmp,
|
||||
@Matches(IsIdentityOperation.class) Function<? super T, ? extends T> keyExtractor) {
|
||||
return cmp.thenComparing(keyExtractor);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import org.checkerframework.checker.interning.qual.Interned;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with (in)equalities. */
|
||||
@@ -21,7 +22,7 @@ final class EqualityRules {
|
||||
|
||||
/** Prefer reference-based quality for enums. */
|
||||
// Primitive value comparisons are not listed, because Error Prone flags those out of the box.
|
||||
static final class PrimitiveOrReferenceEquality<T extends Enum<T>> {
|
||||
static final class PrimitiveOrReferenceEquality<T extends @Interned Enum<T>> {
|
||||
/**
|
||||
* Enums can be compared by reference. It is safe to do so even in the face of refactorings,
|
||||
* because if the type is ever converted to a non-enum, then Error-Prone will complain about any
|
||||
@@ -30,6 +31,7 @@ final class EqualityRules {
|
||||
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
|
||||
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("interning:unnecessary.equals" /* This violation will be rewritten. */)
|
||||
boolean before(T a, T b) {
|
||||
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
|
||||
}
|
||||
@@ -96,7 +98,7 @@ final class EqualityRules {
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(Object a, Object b) {
|
||||
boolean before(@Interned Object a, @Interned Object b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
@@ -127,7 +129,7 @@ final class EqualityRules {
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(Object a, Object b) {
|
||||
boolean before(@Interned Object a, @Interned Object b) {
|
||||
return !(a != b);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
|
||||
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.function.Function.identity;
|
||||
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
@@ -16,6 +15,7 @@ import com.google.common.collect.Streams;
|
||||
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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
@@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableListMultimap}s. */
|
||||
@OnlineDocumentation
|
||||
@@ -173,27 +174,29 @@ final class ImmutableListMultimapRules {
|
||||
* Prefer {@link Multimaps#index(Iterable, com.google.common.base.Function)} over the stream-based
|
||||
* alternative.
|
||||
*/
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
static final class IndexIterableToImmutableListMultimap<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableListMultimap<K, V> before(
|
||||
Iterator<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableListMultimap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
Iterator<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableListMultimap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableListMultimap<K, V> before(
|
||||
Iterable<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableListMultimap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
Iterable<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableListMultimap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableListMultimap<K, V> before(
|
||||
Collection<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return iterable.stream()
|
||||
.collect(toImmutableListMultimap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
Collection<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return iterable.stream().collect(toImmutableListMultimap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
|
||||
@@ -4,7 +4,6 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.function.Function.identity;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -13,6 +12,7 @@ import com.google.common.collect.Streams;
|
||||
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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
@@ -23,6 +23,7 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableMap}s. */
|
||||
@OnlineDocumentation
|
||||
@@ -63,27 +64,29 @@ final class ImmutableMapRules {
|
||||
* Prefer {@link Maps#toMap(Iterable, com.google.common.base.Function)} over more contrived
|
||||
* alternatives.
|
||||
*/
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
static final class IterableToImmutableMap<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(
|
||||
Iterator<K> iterable, Function<? super K, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
|
||||
Iterator<K> iterable,
|
||||
Function<? super K, ? extends V> valueFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super K, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(
|
||||
Iterable<K> iterable, Function<? super K, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
|
||||
Iterable<K> iterable,
|
||||
Function<? super K, ? extends V> valueFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super K, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(
|
||||
Collection<K> iterable, Function<? super K, ? extends V> valueFunction) {
|
||||
return iterable.stream()
|
||||
.collect(toImmutableMap(Refaster.anyOf(identity(), k -> k), valueFunction));
|
||||
Collection<K> iterable,
|
||||
Function<? super K, ? extends V> valueFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super K, ? extends K> keyFunction) {
|
||||
return iterable.stream().collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@@ -157,25 +160,29 @@ final class ImmutableMapRules {
|
||||
* Prefer {@link Maps#uniqueIndex(Iterable, com.google.common.base.Function)} over the
|
||||
* stream-based alternative.
|
||||
*/
|
||||
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
|
||||
static final class IndexIterableToImmutableMap<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(Iterator<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(Iterable<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return Streams.stream(iterable)
|
||||
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
ImmutableMap<K, V> before(
|
||||
Iterator<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(
|
||||
Collection<V> iterable, Function<? super V, ? extends K> keyFunction) {
|
||||
return iterable.stream()
|
||||
.collect(toImmutableMap(keyFunction, Refaster.anyOf(identity(), v -> v)));
|
||||
Iterable<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return Streams.stream(iterable).collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(
|
||||
Collection<V> iterable,
|
||||
Function<? super V, ? extends K> keyFunction,
|
||||
@Matches(IsIdentityOperation.class) Function<? super V, ? extends V> valueFunction) {
|
||||
return iterable.stream().collect(toImmutableMap(keyFunction, valueFunction));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
|
||||
@@ -33,14 +33,12 @@ final class MapRules {
|
||||
|
||||
static final class MapGetOrNull<K, V, T> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
V before(Map<K, V> map, T key) {
|
||||
@Nullable V before(Map<K, V> map, T key) {
|
||||
return map.getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
V after(Map<K, V> map, T key) {
|
||||
@Nullable V after(Map<K, V> map, T key) {
|
||||
return map.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
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 org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.verification.VerificationMode;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@@ -50,4 +52,30 @@ final class MockitoRules {
|
||||
return verify(mock);
|
||||
}
|
||||
}
|
||||
|
||||
static final class InvocationOnMockGetArguments {
|
||||
@BeforeTemplate
|
||||
Object before(InvocationOnMock invocation, int i) {
|
||||
return invocation.getArguments()[i];
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Object after(InvocationOnMock invocation, int i) {
|
||||
return invocation.getArgument(i);
|
||||
}
|
||||
}
|
||||
|
||||
static final class InvocationOnMockGetArgumentsWithTypeParameter<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
T before(InvocationOnMock invocation, int i) {
|
||||
return Refaster.anyOf(
|
||||
invocation.getArgument(i, Refaster.<T>clazz()), (T) invocation.getArgument(i));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(InvocationOnMock invocation, int i) {
|
||||
return invocation.<T>getArgument(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ final class MultimapRules {
|
||||
*/
|
||||
static final class MultimapGet<K, V> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
@Nullable Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
return Refaster.anyOf(multimap.asMap(), Multimaps.asMap(multimap)).get(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import org.checkerframework.checker.regex.qual.Regex;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to code dealing with regular expressions. */
|
||||
@OnlineDocumentation
|
||||
final class PatternRules {
|
||||
private PatternRules() {}
|
||||
|
||||
/** Prefer {@link Pattern#asPredicate()} over non-JDK alternatives. */
|
||||
// XXX: This rule could also replace `s -> pattern.matcher(s).find()`, though the lambda
|
||||
// expression may match functional interfaces other than `Predicate`. If we do add such a rule, we
|
||||
// should also add a rule that replaces `s -> pattern.matcher(s).matches()` with
|
||||
// `pattern.asMatchPredicate()`.
|
||||
static final class PatternAsPredicate {
|
||||
@BeforeTemplate
|
||||
Predicate<CharSequence> before(Pattern pattern) {
|
||||
return Predicates.contains(pattern);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Predicate<String> after(Pattern pattern) {
|
||||
return pattern.asPredicate();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Pattern#asPredicate()} over non-JDK alternatives. */
|
||||
static final class PatternCompileAsPredicate {
|
||||
@BeforeTemplate
|
||||
Predicate<CharSequence> before(@Regex String pattern) {
|
||||
return containsPattern(pattern);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Predicate<String> after(@Regex String pattern) {
|
||||
return Pattern.compile(pattern).asPredicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import com.google.common.primitives.Floats;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.primitives.Shorts;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.AlsoNegation;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@@ -384,4 +386,60 @@ final class PrimitiveRules {
|
||||
return Double.isFinite(d);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer an {@link Integer#signum(int)} comparison to 1 over less clear alternatives. */
|
||||
static final class IntegerSignumIsPositive {
|
||||
@BeforeTemplate
|
||||
boolean before(int i) {
|
||||
return Refaster.anyOf(Integer.signum(i) > 0, Integer.signum(i) >= 1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(int i) {
|
||||
return Integer.signum(i) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer an {@link Integer#signum(int)} comparison to -1 over less clear alternatives. */
|
||||
static final class IntegerSignumIsNegative {
|
||||
@BeforeTemplate
|
||||
boolean before(int i) {
|
||||
return Refaster.anyOf(Integer.signum(i) < 0, Integer.signum(i) <= -1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(int i) {
|
||||
return Integer.signum(i) == -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer an {@link Long#signum(long)} comparison to 1 over less clear alternatives. */
|
||||
static final class LongSignumIsPositive {
|
||||
@BeforeTemplate
|
||||
boolean before(long l) {
|
||||
return Refaster.anyOf(Long.signum(l) > 0, Long.signum(l) >= 1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(long l) {
|
||||
return Long.signum(l) == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer an {@link Long#signum(long)} comparison to -1 over less clear alternatives. */
|
||||
static final class LongSignumIsNegative {
|
||||
@BeforeTemplate
|
||||
boolean before(long l) {
|
||||
return Refaster.anyOf(Long.signum(l) < 0, Long.signum(l) <= -1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@AlsoNegation
|
||||
boolean after(long l) {
|
||||
return Long.signum(l) == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
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.Matches;
|
||||
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
|
||||
import com.google.errorprone.refaster.annotation.NotMatches;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
@@ -26,8 +26,8 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.BiConsumer;
|
||||
@@ -48,6 +48,8 @@ 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.IsEmpty;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
|
||||
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;
|
||||
|
||||
/** Refaster rules related to Reactor expressions and statements. */
|
||||
@@ -98,7 +100,7 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#justOrEmpty(Object)} over more contrived alternatives. */
|
||||
static final class MonoJustOrEmptyObject<@Nullable T> {
|
||||
static final class MonoJustOrEmptyObject<T extends @Nullable Object> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(T value) {
|
||||
return Mono.justOrEmpty(Optional.ofNullable(value));
|
||||
@@ -247,11 +249,43 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#zipWithIterable(Iterable)} over more contrived alternatives. */
|
||||
static final class FluxZipWithIterable<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<Tuple2<T, S>> before(Flux<T> flux, Iterable<S> iterable) {
|
||||
return Flux.zip(flux, Flux.fromIterable(iterable));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<Tuple2<T, S>> after(Flux<T> flux, Iterable<S> iterable) {
|
||||
return flux.zipWithIterable(iterable);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#zipWithIterable(Iterable, BiFunction)} over more contrived alternatives. */
|
||||
static final class FluxZipWithIterableBiFunction<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(
|
||||
Flux<T> flux,
|
||||
Iterable<S> iterable,
|
||||
BiFunction<? super T, ? super S, ? extends R> function) {
|
||||
return flux.zipWith(Flux.fromIterable(iterable), function);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<R> after(
|
||||
Flux<T> flux,
|
||||
Iterable<S> iterable,
|
||||
BiFunction<? super T, ? super S, ? extends R> function) {
|
||||
return flux.zipWithIterable(iterable, function);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
static final class FluxZipWithIterableMapFunction<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWithIterable(iterable, combinator);
|
||||
@@ -328,7 +362,10 @@ final class ReactorRules {
|
||||
static final class MonoThenReturn<T, S> {
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono, S object) {
|
||||
return mono.then(Mono.just(object));
|
||||
return Refaster.anyOf(
|
||||
mono.ignoreElement().thenReturn(object),
|
||||
mono.then().thenReturn(object),
|
||||
mono.then(Mono.just(object)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -391,6 +428,74 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#empty()} over more contrived alternatives. */
|
||||
// XXX: In combination with the `IsEmpty` matcher introduced by
|
||||
// https://github.com/PicnicSupermarket/error-prone-support/pull/744, the non-varargs overloads of
|
||||
// most methods referenced here can be rewritten as well. Additionally, some invocations of
|
||||
// methods such as `Flux#fromIterable`, `Flux#fromArray` and `Flux#justOrEmpty` can also be
|
||||
// rewritten.
|
||||
static final class FluxEmpty<T, S extends Comparable<? super S>> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
int prefetch,
|
||||
Function<? super Object[], ? extends T> combinator,
|
||||
Comparator<? super T> comparator) {
|
||||
return Refaster.anyOf(
|
||||
Flux.concat(),
|
||||
Flux.concatDelayError(),
|
||||
Flux.firstWithSignal(),
|
||||
Flux.just(),
|
||||
Flux.merge(),
|
||||
Flux.merge(prefetch),
|
||||
Flux.mergeComparing(comparator),
|
||||
Flux.mergeComparing(prefetch, comparator),
|
||||
Flux.mergeComparingDelayError(prefetch, comparator),
|
||||
Flux.mergeDelayError(prefetch),
|
||||
Flux.mergePriority(comparator),
|
||||
Flux.mergePriority(prefetch, comparator),
|
||||
Flux.mergePriorityDelayError(prefetch, comparator),
|
||||
Flux.mergeSequential(),
|
||||
Flux.mergeSequential(prefetch),
|
||||
Flux.mergeSequentialDelayError(prefetch),
|
||||
Flux.zip(combinator),
|
||||
Flux.zip(combinator, prefetch));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<T> before(int prefetch, Function<Object[], T> combinator) {
|
||||
return Refaster.anyOf(
|
||||
Flux.combineLatest(combinator), Flux.combineLatest(combinator, prefetch));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<S> before() {
|
||||
return Refaster.anyOf(Flux.mergeComparing(), Flux.mergePriority());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<Integer> before(int start) {
|
||||
return Flux.range(start, 0);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after() {
|
||||
return Flux.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#just(Object)} over more contrived alternatives. */
|
||||
static final class FluxJust {
|
||||
@BeforeTemplate
|
||||
Flux<Integer> before(int start) {
|
||||
return Flux.range(start, 1);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<Integer> after(int start) {
|
||||
return Flux.just(start);
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily transform a {@link Mono} to an equivalent instance. */
|
||||
static final class MonoIdentity<T> {
|
||||
@BeforeTemplate
|
||||
@@ -401,11 +506,12 @@ final class ReactorRules {
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before2(Mono<@Nullable Void> mono) {
|
||||
return mono.then();
|
||||
return Refaster.anyOf(mono.ignoreElement(), mono.then());
|
||||
}
|
||||
|
||||
// XXX: Replace this rule with an extension of the `IdentityConversion` rule, supporting
|
||||
// `Stream#map`, `Mono#map` and `Flux#map`.
|
||||
// `Stream#map`, `Mono#map` and `Flux#map`. Alternatively, extend the `IsIdentityOperation`
|
||||
// matcher and use it to constrain the matched `map` argument.
|
||||
@BeforeTemplate
|
||||
Mono<ImmutableList<T>> before3(Mono<ImmutableList<T>> mono) {
|
||||
return mono.map(ImmutableList::copyOf);
|
||||
@@ -446,53 +552,97 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#concatMap(Function)} over more contrived alternatives. */
|
||||
static final class FluxConcatMap<T, S> {
|
||||
static final class FluxConcatMap<T, S, P extends Publisher<? extends S>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedPublishers")
|
||||
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
|
||||
Flux<S> before(
|
||||
Flux<T> flux,
|
||||
Function<? super T, ? extends P> function,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super P, ? extends Publisher<? extends S>> identityOperation) {
|
||||
return Refaster.anyOf(
|
||||
flux.flatMap(function, 1),
|
||||
flux.flatMapSequential(function, 1),
|
||||
flux.map(function).concatMap(identity()));
|
||||
flux.map(function).concatMap(identityOperation));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
|
||||
Flux<S> after(Flux<T> flux, Function<? super T, ? extends P> function) {
|
||||
return flux.concatMap(function);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#concatMap(Function, int)} over more contrived alternatives. */
|
||||
static final class FluxConcatMapWithPrefetch<T, S> {
|
||||
static final class FluxConcatMapWithPrefetch<T, S, P extends Publisher<? extends S>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedPublishers")
|
||||
Flux<S> before(
|
||||
Flux<T> flux,
|
||||
Function<? super T, ? extends Publisher<? extends S>> function,
|
||||
int prefetch) {
|
||||
Function<? super T, ? extends P> function,
|
||||
int prefetch,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super P, ? extends Publisher<? extends S>> identityOperation) {
|
||||
return Refaster.anyOf(
|
||||
flux.flatMap(function, 1, prefetch),
|
||||
flux.flatMapSequential(function, 1, prefetch),
|
||||
flux.map(function).concatMap(identity(), prefetch));
|
||||
flux.map(function).concatMap(identityOperation, prefetch));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(
|
||||
Flux<T> flux,
|
||||
Function<? super T, ? extends Publisher<? extends S>> function,
|
||||
int prefetch) {
|
||||
Flux<S> after(Flux<T> flux, Function<? super T, ? extends P> function, int prefetch) {
|
||||
return flux.concatMap(function, prefetch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#concatMapIterable(Function)} over {@link Flux#flatMapIterable(Function)}, as
|
||||
* the former has equivalent semantics but a clearer name.
|
||||
*/
|
||||
static final class FluxConcatMapIterable<T, S> {
|
||||
/** Avoid contrived alternatives to {@link Mono#flatMapIterable(Function)}. */
|
||||
static final class MonoFlatMapIterable<T, S, I extends Iterable<? extends S>> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Iterable<? extends S>> function) {
|
||||
return flux.flatMapIterable(function);
|
||||
Flux<S> before(
|
||||
Mono<T> mono,
|
||||
Function<? super T, I> function,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super I, ? extends Iterable<? extends S>> identityOperation) {
|
||||
return Refaster.anyOf(
|
||||
mono.map(function).flatMapIterable(identityOperation),
|
||||
mono.flux().concatMapIterable(function));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Mono<T> mono, Function<? super T, ? extends Iterable<? extends S>> function) {
|
||||
return mono.flatMapIterable(function);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#flatMapIterable(Function)} to flatten a {@link Mono} of some {@link
|
||||
* Iterable} over less efficient alternatives.
|
||||
*/
|
||||
static final class MonoFlatMapIterableIdentity<T, S extends Iterable<T>> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Mono<S> mono) {
|
||||
return mono.flatMapMany(Flux::fromIterable);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
Flux<T> after(Mono<S> mono) {
|
||||
return mono.flatMapIterable(identity());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#concatMapIterable(Function)} over alternatives with less clear syntax or
|
||||
* semantics.
|
||||
*/
|
||||
static final class FluxConcatMapIterable<T, S, I extends Iterable<? extends S>> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(
|
||||
Flux<T> flux,
|
||||
Function<? super T, I> function,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super I, ? extends Iterable<? extends S>> identityOperation) {
|
||||
return Refaster.anyOf(
|
||||
flux.flatMapIterable(function), flux.map(function).concatMapIterable(identityOperation));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -502,14 +652,20 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#concatMapIterable(Function, int)} over {@link Flux#flatMapIterable(Function,
|
||||
* int)}, as the former has equivalent semantics but a clearer name.
|
||||
* Prefer {@link Flux#concatMapIterable(Function, int)} over alternatives with less clear syntax
|
||||
* or semantics.
|
||||
*/
|
||||
static final class FluxConcatMapIterableWithPrefetch<T, S> {
|
||||
static final class FluxConcatMapIterableWithPrefetch<T, S, I extends Iterable<? extends S>> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(
|
||||
Flux<T> flux, Function<? super T, ? extends Iterable<? extends S>> function, int prefetch) {
|
||||
return flux.flatMapIterable(function, prefetch);
|
||||
Flux<T> flux,
|
||||
Function<? super T, I> function,
|
||||
int prefetch,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super I, ? extends Iterable<? extends S>> identityOperation) {
|
||||
return Refaster.anyOf(
|
||||
flux.flatMapIterable(function, prefetch),
|
||||
flux.map(function).concatMapIterable(identityOperation, prefetch));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -741,7 +897,7 @@ final class ReactorRules {
|
||||
static final class MonoThen<T> {
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before(Mono<T> mono) {
|
||||
return mono.flux().then();
|
||||
return Refaster.anyOf(mono.ignoreElement().then(), mono.flux().then());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -750,6 +906,128 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
|
||||
static final class FluxThen<T> {
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before(Flux<T> flux) {
|
||||
return flux.ignoreElements().then();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before2(Flux<@Nullable Void> flux) {
|
||||
return flux.ignoreElements();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<@Nullable Void> after(Flux<T> flux) {
|
||||
return flux.then();
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Mono#ignoreElement()}. */
|
||||
static final class MonoThenEmpty<T> {
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before(Mono<T> mono, Publisher<@Nullable Void> publisher) {
|
||||
return mono.ignoreElement().thenEmpty(publisher);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<@Nullable Void> after(Mono<T> mono, Publisher<@Nullable Void> publisher) {
|
||||
return mono.thenEmpty(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
|
||||
static final class FluxThenEmpty<T> {
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before(Flux<T> flux, Publisher<@Nullable Void> publisher) {
|
||||
return flux.ignoreElements().thenEmpty(publisher);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<@Nullable Void> after(Flux<T> flux, Publisher<@Nullable Void> publisher) {
|
||||
return flux.thenEmpty(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Mono#ignoreElement()}. */
|
||||
static final class MonoThenMany<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Mono<T> mono, Publisher<S> publisher) {
|
||||
return mono.ignoreElement().thenMany(publisher);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Mono<T> mono, Publisher<S> publisher) {
|
||||
return mono.thenMany(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer explicit invocation of {@link Mono#flux()} over implicit conversions from {@link Mono}
|
||||
* to {@link Flux}.
|
||||
*/
|
||||
static final class MonoThenMonoFlux<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Mono<T> mono1, Mono<S> mono2) {
|
||||
return mono1.thenMany(mono2);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Mono<T> mono1, Mono<S> mono2) {
|
||||
return mono1.then(mono2).flux();
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
|
||||
static final class FluxThenMany<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Flux<T> flux, Publisher<S> publisher) {
|
||||
return flux.ignoreElements().thenMany(publisher);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux, Publisher<S> publisher) {
|
||||
return flux.thenMany(publisher);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Mono#ignoreElement()}. */
|
||||
static final class MonoThenMono<T, S> {
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono1, Mono<S> mono2) {
|
||||
return mono1.ignoreElement().then(mono2);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before2(Mono<T> mono1, Mono<@Nullable Void> mono2) {
|
||||
return mono1.thenEmpty(mono2);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Mono<T> mono1, Mono<S> mono2) {
|
||||
return mono1.then(mono2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
|
||||
static final class FluxThenMono<T, S> {
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Flux<T> flux, Mono<S> mono) {
|
||||
return flux.ignoreElements().then(mono);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<@Nullable Void> before2(Flux<T> flux, Mono<@Nullable Void> mono) {
|
||||
return flux.thenEmpty(mono);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Flux<T> flux, Mono<S> mono) {
|
||||
return flux.then(mono);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#singleOptional()} over more contrived alternatives. */
|
||||
// XXX: Consider creating a plugin that flags/discourages `Mono<Optional<T>>` method return
|
||||
// types, just as we discourage nullable `Boolean`s and `Optional`s.
|
||||
@@ -794,30 +1072,84 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#flatMap(Function)} over more contrived alternatives. */
|
||||
static final class MonoFlatMap<S, T> {
|
||||
/** Prefer {@link Mono#ofType(Class)} over more contrived alternatives. */
|
||||
static final class MonoOfType<T, S> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedPublishers")
|
||||
Mono<T> before(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
|
||||
return mono.map(function).flatMap(identity());
|
||||
Mono<S> before(Mono<T> mono, Class<S> clazz) {
|
||||
return mono.filter(clazz::isInstance).cast(clazz);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
|
||||
Mono<S> after(Mono<T> mono, Class<S> clazz) {
|
||||
return mono.ofType(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#ofType(Class)} over more contrived alternatives. */
|
||||
static final class FluxOfType<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Flux<T> flux, Class<S> clazz) {
|
||||
return flux.filter(clazz::isInstance).cast(clazz);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux, Class<S> clazz) {
|
||||
return flux.ofType(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#flatMap(Function)} over more contrived alternatives. */
|
||||
static final class MonoFlatMap<S, T, P extends Mono<? extends T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedPublishers")
|
||||
Mono<T> before(
|
||||
Mono<S> mono,
|
||||
Function<? super S, ? extends P> function,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super P, ? extends Mono<? extends T>> identityOperation) {
|
||||
return mono.map(function).flatMap(identityOperation);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<S> mono, Function<? super S, ? extends P> function) {
|
||||
return mono.flatMap(function);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#flatMapMany(Function)} over more contrived alternatives. */
|
||||
static final class MonoFlatMapMany<S, T> {
|
||||
static final class MonoFlatMapMany<S, T, P extends Publisher<? extends T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedPublishers")
|
||||
Flux<T> before(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
|
||||
return mono.map(function).flatMapMany(identity());
|
||||
Flux<T> before(
|
||||
Mono<S> mono,
|
||||
Function<? super S, P> function,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super P, ? extends Publisher<? extends T>> identityOperation,
|
||||
boolean delayUntilEnd,
|
||||
int maxConcurrency,
|
||||
int prefetch) {
|
||||
return Refaster.anyOf(
|
||||
mono.map(function).flatMapMany(identityOperation),
|
||||
mono.flux().concatMap(function),
|
||||
mono.flux().concatMap(function, prefetch),
|
||||
mono.flux().concatMapDelayError(function),
|
||||
mono.flux().concatMapDelayError(function, prefetch),
|
||||
mono.flux().concatMapDelayError(function, delayUntilEnd, prefetch),
|
||||
mono.flux().flatMap(function, maxConcurrency),
|
||||
mono.flux().flatMap(function, maxConcurrency, prefetch),
|
||||
mono.flux().flatMapDelayError(function, maxConcurrency, prefetch),
|
||||
mono.flux().flatMapSequential(function, maxConcurrency),
|
||||
mono.flux().flatMapSequential(function, maxConcurrency, prefetch),
|
||||
mono.flux().flatMapSequentialDelayError(function, maxConcurrency, prefetch));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Mono<S> mono, Function<? super S, Publisher<? extends T>> function) {
|
||||
return mono.flux().switchMap(function);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
|
||||
Flux<T> after(Mono<S> mono, Function<? super S, ? extends P> function) {
|
||||
return mono.flatMapMany(function);
|
||||
}
|
||||
}
|
||||
@@ -1248,12 +1580,12 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/** 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.
|
||||
// XXX: Introduce Refaster rules or a `BugChecker` that maps `(Immutable)Map.of(k, v)` to
|
||||
// `Context.of(k, v)` and likewise for multi-pair overloads.
|
||||
static final class ContextEmpty {
|
||||
@BeforeTemplate
|
||||
Context before() {
|
||||
return Context.of(Refaster.anyOf(new HashMap<>(), ImmutableMap.of()));
|
||||
Context before(@Matches(IsEmpty.class) Map<?, ?> map) {
|
||||
return Context.of(map);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -1302,13 +1634,13 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */
|
||||
// XXX: Given an `IsEmpty` matcher that identifies a wide range of guaranteed-empty `Iterable`
|
||||
// expressions, consider also simplifying `step.expectNextSequence(someEmptyIterable)`.
|
||||
static final class StepVerifierStepIdentity<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
StepVerifier.Step<T> before(StepVerifier.Step<T> step) {
|
||||
return Refaster.anyOf(step.expectNext(), step.expectNextCount(0));
|
||||
StepVerifier.Step<T> before(
|
||||
StepVerifier.Step<T> step, @Matches(IsEmpty.class) Iterable<? extends T> iterable) {
|
||||
return Refaster.anyOf(
|
||||
step.expectNext(), step.expectNextCount(0), step.expectNextSequence(iterable));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -1381,6 +1713,7 @@ final class ReactorRules {
|
||||
Duration before(StepVerifier.LastStep step, Class<T> clazz) {
|
||||
return Refaster.anyOf(
|
||||
step.expectError(clazz).verify(),
|
||||
step.verifyErrorMatches(clazz::isInstance),
|
||||
step.verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(clazz)));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Comparator.naturalOrder;
|
||||
import static java.util.Comparator.reverseOrder;
|
||||
import static java.util.function.Function.identity;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static java.util.stream.Collectors.counting;
|
||||
import static java.util.stream.Collectors.filtering;
|
||||
@@ -20,6 +19,7 @@ import static java.util.stream.Collectors.summingDouble;
|
||||
import static java.util.stream.Collectors.summingInt;
|
||||
import static java.util.stream.Collectors.summingLong;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
@@ -50,6 +50,7 @@ import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsRefasterAsVarargs;
|
||||
|
||||
@@ -356,6 +357,8 @@ final class StreamRules {
|
||||
stream.filter(predicate).findAny().isEmpty());
|
||||
}
|
||||
|
||||
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
|
||||
// template's `Refaster.anyOf` usage.
|
||||
@BeforeTemplate
|
||||
boolean before2(
|
||||
Stream<T> stream,
|
||||
@@ -394,6 +397,8 @@ final class StreamRules {
|
||||
!stream.noneMatch(predicate), stream.filter(predicate).findAny().isPresent());
|
||||
}
|
||||
|
||||
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
|
||||
// template's `Refaster.anyOf` usage.
|
||||
@BeforeTemplate
|
||||
boolean before2(
|
||||
Stream<T> stream,
|
||||
@@ -414,6 +419,8 @@ final class StreamRules {
|
||||
return stream.noneMatch(Refaster.anyOf(not(predicate), predicate.negate()));
|
||||
}
|
||||
|
||||
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
|
||||
// template's `Refaster.anyOf` usage.
|
||||
@BeforeTemplate
|
||||
boolean before2(
|
||||
Stream<T> stream,
|
||||
@@ -445,7 +452,11 @@ final class StreamRules {
|
||||
|
||||
static final class StreamMapToIntSum<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
|
||||
@SuppressWarnings({
|
||||
"interning:return" /* Handle inference failure. */,
|
||||
"java:S4266" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
long before(Stream<T> stream, ToIntFunction<T> mapper) {
|
||||
return stream.collect(summingInt(mapper));
|
||||
}
|
||||
@@ -465,7 +476,11 @@ final class StreamRules {
|
||||
|
||||
static final class StreamMapToDoubleSum<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
|
||||
@SuppressWarnings({
|
||||
"interning:return" /* Handle inference failure. */,
|
||||
"java:S4266" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
double before(Stream<T> stream, ToDoubleFunction<T> mapper) {
|
||||
return stream.collect(summingDouble(mapper));
|
||||
}
|
||||
@@ -485,7 +500,11 @@ final class StreamRules {
|
||||
|
||||
static final class StreamMapToLongSum<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
|
||||
@SuppressWarnings({
|
||||
"interning:return" /* Handle inference failure. */,
|
||||
"java:S4266" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
long before(Stream<T> stream, ToLongFunction<T> mapper) {
|
||||
return stream.collect(summingLong(mapper));
|
||||
}
|
||||
@@ -541,7 +560,11 @@ final class StreamRules {
|
||||
|
||||
static final class StreamCount<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
|
||||
@SuppressWarnings({
|
||||
"interning:return" /* Handle inference failure. */,
|
||||
"java:S4266" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
long before(Stream<T> stream) {
|
||||
return stream.collect(counting());
|
||||
}
|
||||
@@ -631,8 +654,11 @@ final class StreamRules {
|
||||
|
||||
static final class StreamsConcat<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(@Repeated Stream<T> stream) {
|
||||
return Stream.of(Refaster.asVarargs(stream)).flatMap(Refaster.anyOf(identity(), s -> s));
|
||||
Stream<T> before(
|
||||
@Repeated Stream<T> stream,
|
||||
@Matches(IsIdentityOperation.class)
|
||||
Function<? super Stream<T>, ? extends Stream<? extends T>> mapper) {
|
||||
return Stream.of(Refaster.asVarargs(stream)).flatMap(mapper);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -668,4 +694,74 @@ final class StreamRules {
|
||||
return Stream.iterate(seed, hasNext, next);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Stream#of(Object)} over more contrived alternatives. */
|
||||
// XXX: Generalize this and similar rules using an Error Prone check.
|
||||
static final class StreamOf1<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(T e1) {
|
||||
return ImmutableList.of(e1).stream();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<T> after(T e1) {
|
||||
return Stream.of(e1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
|
||||
// XXX: Generalize this and similar rules using an Error Prone check.
|
||||
static final class StreamOf2<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(T e1, T e2) {
|
||||
return ImmutableList.of(e1, e2).stream();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<T> after(T e1, T e2) {
|
||||
return Stream.of(e1, e2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
|
||||
// XXX: Generalize this and similar rules using an Error Prone check.
|
||||
static final class StreamOf3<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(T e1, T e2, T e3) {
|
||||
return ImmutableList.of(e1, e2, e3).stream();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<T> after(T e1, T e2, T e3) {
|
||||
return Stream.of(e1, e2, e3);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
|
||||
// XXX: Generalize this and similar rules using an Error Prone check.
|
||||
static final class StreamOf4<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(T e1, T e2, T e3, T e4) {
|
||||
return ImmutableList.of(e1, e2, e3, e4).stream();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<T> after(T e1, T e2, T e3, T e4) {
|
||||
return Stream.of(e1, e2, e3, e4);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
|
||||
// XXX: Generalize this and similar rules using an Error Prone check.
|
||||
static final class StreamOf5<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(T e1, T e2, T e3, T e4, T e5) {
|
||||
return ImmutableList.of(e1, e2, e3, e4, e5).stream();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<T> after(T e1, T e2, T e3, T e4, T e5) {
|
||||
return Stream.of(e1, e2, e3, e4, e5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
@@ -12,7 +10,7 @@ final class AmbiguousJsonCreatorTest {
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"X", containsPattern("`JsonCreator.Mode` should be set for single-argument creators"))
|
||||
"X", m -> m.contains("`JsonCreator.Mode` should be set for single-argument creators"))
|
||||
.addSourceLines(
|
||||
"Container.java",
|
||||
"import com.fasterxml.jackson.annotation.JsonCreator;",
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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 CanonicalClassNameUsageTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(CanonicalClassNameUsage.class, getClass())
|
||||
.setArgs(
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
|
||||
"",
|
||||
"import com.google.errorprone.VisitorState;",
|
||||
"import tech.picnic.errorprone.bugpatterns.util.MoreTypes;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m(VisitorState state) {",
|
||||
" String a = A.class.getName();",
|
||||
" String b = getClass().getName();",
|
||||
" A.class.getName().toString();",
|
||||
" System.out.println(A.class.getName());",
|
||||
" methodInUnnamedPackage(A.class.getName());",
|
||||
" instanceMethod().onExactClass(A.class.getCanonicalName());",
|
||||
" MoreTypes.type(A.class.getCanonicalName());",
|
||||
" MoreTypes.type(A.class.getCanonicalName() + \".SubType\");",
|
||||
" instanceMethod().onExactClass(new Object() {}.getClass().getName());",
|
||||
" instanceMethod().onExactClass(methodInUnnamedPackage(A.class.getName()));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" instanceMethod().onExactClass(A.class.getName());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" MoreTypes.type(A.class.getName());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" state.binaryNameFromClassname(A.class.getName() + \".SubType\");",
|
||||
" }",
|
||||
"",
|
||||
" String methodInUnnamedPackage(String str) {",
|
||||
" return str;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(CanonicalClassNameUsage.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
|
||||
"",
|
||||
"import com.google.errorprone.BugPattern;",
|
||||
"import tech.picnic.errorprone.bugpatterns.util.MoreTypes;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" instanceMethod().onDescendantOfAny(A.class.getName(), BugPattern.LinkType.class.getName());",
|
||||
" MoreTypes.type(String.class.getName());",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
|
||||
"",
|
||||
"import com.google.errorprone.BugPattern;",
|
||||
"import tech.picnic.errorprone.bugpatterns.util.MoreTypes;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" instanceMethod()",
|
||||
" .onDescendantOfAny(",
|
||||
" A.class.getCanonicalName(), BugPattern.LinkType.class.getCanonicalName());",
|
||||
" MoreTypes.type(String.class.getCanonicalName());",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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 ErrorProneRuntimeClasspathTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(ErrorProneRuntimeClasspath.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"USE_CLASS_REFERENCE",
|
||||
m ->
|
||||
m.contains(
|
||||
"This type will be on the runtime classpath; use `Class#getCanonicalName()` instead"))
|
||||
.expectErrorMessage(
|
||||
"USE_STRING_LITERAL",
|
||||
m ->
|
||||
m.contains(
|
||||
"This type may not be on the runtime classpath; use a string literal instead"))
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.BugPattern;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" @SuppressWarnings(\"java.lang.String\")",
|
||||
" void m(Object o) {",
|
||||
" m(null);",
|
||||
" m(0);",
|
||||
" m(getClass().getName());",
|
||||
" m(getClass().getCanonicalName());",
|
||||
" m(\"\");",
|
||||
" m(\"foo\");",
|
||||
" m(\"java.util.\");",
|
||||
"",
|
||||
" m(\"org.junit.jupiter.api.Test\");",
|
||||
" m(\"org.junit.jupiter.api.Test.toString\");",
|
||||
" m(\"com.google.errorprone.CompilationTestHelper\");",
|
||||
" m(\"com.google.errorprone.CompilationTestHelper.toString\");",
|
||||
" m(\"com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput\");",
|
||||
" m(\"com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput.toString\");",
|
||||
" m(\"com.google.errorprone.NonExistent\");",
|
||||
" m(\"com.google.common.NonExistent.toString\");",
|
||||
" m(\"java.lang.NonExistent\");",
|
||||
" // BUG: Diagnostic matches: USE_CLASS_REFERENCE",
|
||||
" m(\"com.google.errorprone.BugPattern\");",
|
||||
" // BUG: Diagnostic matches: USE_CLASS_REFERENCE",
|
||||
" m(\"com.google.errorprone.util.ErrorProneToken\");",
|
||||
" // BUG: Diagnostic matches: USE_CLASS_REFERENCE",
|
||||
" m(\"com.google.common.collect.ImmutableList\");",
|
||||
" // BUG: Diagnostic matches: USE_CLASS_REFERENCE",
|
||||
" m(\"java.lang.String\");",
|
||||
" // BUG: Diagnostic matches: USE_CLASS_REFERENCE",
|
||||
" m(\"java.lang.String.toString\");",
|
||||
"",
|
||||
" m(BugPattern.class.getCanonicalName());",
|
||||
" m(ImmutableList.class.getCanonicalName());",
|
||||
" m(String.class.getCanonicalName());",
|
||||
" m(void.class.getCanonicalName());",
|
||||
" m(boolean.class.getCanonicalName());",
|
||||
" m(byte.class.getCanonicalName());",
|
||||
" m(char.class.getCanonicalName());",
|
||||
" m(short.class.getCanonicalName());",
|
||||
" m(int.class.getCanonicalName());",
|
||||
" m(long.class.getCanonicalName());",
|
||||
" m(float.class.getCanonicalName());",
|
||||
" m(double.class.getCanonicalName());",
|
||||
" m(java.lang.Iterable.class.getCanonicalName());",
|
||||
" m(CompilationTestHelper.class.toString());",
|
||||
" // BUG: Diagnostic matches: USE_STRING_LITERAL",
|
||||
" m(CompilationTestHelper.class.getCanonicalName());",
|
||||
" // BUG: Diagnostic matches: USE_STRING_LITERAL",
|
||||
" m(BugCheckerRefactoringTestHelper.ExpectOutput.class.getCanonicalName());",
|
||||
" // BUG: Diagnostic matches: USE_STRING_LITERAL",
|
||||
" m(Test.class.getCanonicalName());",
|
||||
" // BUG: Diagnostic matches: USE_STRING_LITERAL",
|
||||
" m(org.junit.jupiter.api.Nested.class.getCanonicalName());",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(ErrorProneRuntimeClasspath.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m(Object o) {",
|
||||
" m(\"com.google.errorprone.BugPattern\");",
|
||||
" m(\"com.google.errorprone.util.ErrorProneToken\");",
|
||||
" m(\"com.google.common.collect.ImmutableList\");",
|
||||
" m(\"java.lang.String\");",
|
||||
" m(\"java.lang.String.toString\");",
|
||||
"",
|
||||
" m(CompilationTestHelper.class.getCanonicalName());",
|
||||
" m(BugCheckerRefactoringTestHelper.ExpectOutput.class.getCanonicalName());",
|
||||
" m(Test.class.getCanonicalName());",
|
||||
" m(org.junit.jupiter.api.Nested.class.getCanonicalName());",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.BugPattern;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.util.ErrorProneToken;",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m(Object o) {",
|
||||
" m(BugPattern.class.getCanonicalName());",
|
||||
" m(ErrorProneToken.class.getCanonicalName());",
|
||||
" m(ImmutableList.class.getCanonicalName());",
|
||||
" m(String.class.getCanonicalName());",
|
||||
" m(String.class.getCanonicalName() + \".toString\");",
|
||||
"",
|
||||
" m(\"com.google.errorprone.CompilationTestHelper\");",
|
||||
" m(\"com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput\");",
|
||||
" m(\"org.junit.jupiter.api.Test\");",
|
||||
" m(\"org.junit.jupiter.api.Nested\");",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.and;
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
import static com.google.common.base.Predicates.not;
|
||||
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;
|
||||
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.THIRD;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.CorePublisher;
|
||||
@@ -20,10 +18,7 @@ final class FluxImplicitBlockTest {
|
||||
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"X",
|
||||
and(
|
||||
containsPattern("SuppressWarnings"),
|
||||
containsPattern("toImmutableList"),
|
||||
containsPattern("toList")))
|
||||
m -> Stream.of("SuppressWarnings", "toImmutableList", "toList").allMatch(m::contains))
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
@@ -63,7 +58,7 @@ final class FluxImplicitBlockTest {
|
||||
void identificationWithoutGuavaOnClasspath() {
|
||||
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
|
||||
.withClasspath(CorePublisher.class, Flux.class, Publisher.class)
|
||||
.expectErrorMessage("X", not(containsPattern("toImmutableList")))
|
||||
.expectErrorMessage("X", m -> !m.contains("toImmutableList"))
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
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 JUnitNullaryParameterizedTestDeclarationTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(JUnitNullaryParameterizedTestDeclaration.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"import org.junit.jupiter.params.provider.ValueSource;",
|
||||
"",
|
||||
"class A {",
|
||||
" void nonTest() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void nonParameterizedTest() {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" void goodParameterizedTest(int someInt) {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void nullaryParameterizedTest() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
JUnitNullaryParameterizedTestDeclaration.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsProvider;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsSource;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsSources;",
|
||||
"import org.junit.jupiter.params.provider.MethodSource;",
|
||||
"import org.junit.jupiter.params.provider.ValueSource;",
|
||||
"",
|
||||
"class A {",
|
||||
" @ParameterizedTest",
|
||||
" void withoutArgumentSource() {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @ArgumentsSource(ArgumentsProvider.class)",
|
||||
" void withCustomArgumentSource() {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @ArgumentsSources({",
|
||||
" @ArgumentsSource(ArgumentsProvider.class),",
|
||||
" @ArgumentsSource(ArgumentsProvider.class)",
|
||||
" })",
|
||||
" void withCustomerArgumentSources() {}",
|
||||
"",
|
||||
" /** Foo. */",
|
||||
" @ParameterizedTest",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" void withValueSourceAndJavadoc() {}",
|
||||
"",
|
||||
" @ParameterizedTest",
|
||||
" @MethodSource(\"nonexistentMethod\")",
|
||||
" @SuppressWarnings(\"foo\")",
|
||||
" void withMethodSourceAndUnrelatedAnnotation() {}",
|
||||
"",
|
||||
" @org.junit.jupiter.params.ParameterizedTest",
|
||||
" @ArgumentsSource(ArgumentsProvider.class)",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" @MethodSource(\"nonexistentMethod\")",
|
||||
" void withMultipleArgumentSourcesAndFullyQualifiedImport() {}",
|
||||
"",
|
||||
" class NestedWithTestAnnotationFirst {",
|
||||
" @ParameterizedTest",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" void withValueSource() {}",
|
||||
" }",
|
||||
"",
|
||||
" class NestedWithTestAnnotationSecond {",
|
||||
" @ValueSource(ints = {0, 1})",
|
||||
" @ParameterizedTest",
|
||||
" void withValueSource() {}",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import org.junit.jupiter.api.Test;",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsProvider;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsSource;",
|
||||
"import org.junit.jupiter.params.provider.ArgumentsSources;",
|
||||
"import org.junit.jupiter.params.provider.MethodSource;",
|
||||
"import org.junit.jupiter.params.provider.ValueSource;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Test",
|
||||
" void withoutArgumentSource() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void withCustomArgumentSource() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void withCustomerArgumentSources() {}",
|
||||
"",
|
||||
" /** Foo. */",
|
||||
" @Test",
|
||||
" void withValueSourceAndJavadoc() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" @SuppressWarnings(\"foo\")",
|
||||
" void withMethodSourceAndUnrelatedAnnotation() {}",
|
||||
"",
|
||||
" @Test",
|
||||
" void withMultipleArgumentSourcesAndFullyQualifiedImport() {}",
|
||||
"",
|
||||
" class NestedWithTestAnnotationFirst {",
|
||||
" @Test",
|
||||
" void withValueSource() {}",
|
||||
" }",
|
||||
"",
|
||||
" class NestedWithTestAnnotationSecond {",
|
||||
" @Test",
|
||||
" void withValueSource() {}",
|
||||
" }",
|
||||
"}")
|
||||
.addInputLines(
|
||||
"B.java",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"",
|
||||
"class B {",
|
||||
" @ParameterizedTest",
|
||||
" void scopeInWhichIdentifierTestIsAlreadyDeclared() {}",
|
||||
"",
|
||||
" class Test {}",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"B.java",
|
||||
"import org.junit.jupiter.params.ParameterizedTest;",
|
||||
"",
|
||||
"class B {",
|
||||
" @org.junit.jupiter.api.Test",
|
||||
" void scopeInWhichIdentifierTestIsAlreadyDeclared() {}",
|
||||
"",
|
||||
" class Test {}",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class NonStaticImportTest {
|
||||
@Test
|
||||
void candidateTypesDoNotClash() {
|
||||
assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES)
|
||||
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidateMembersAreNotRedundant() {
|
||||
assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.keySet())
|
||||
.doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES);
|
||||
|
||||
assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.values())
|
||||
.doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidateMembersDoNotClash() {
|
||||
assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.entries())
|
||||
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.entries());
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidateIdentifiersDoNotClash() {
|
||||
assertThat(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS)
|
||||
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.values());
|
||||
}
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(NonStaticImport.class, getClass())
|
||||
.addSourceLines(
|
||||
"pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static com.google.common.base.Strings.nullToEmpty;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static com.google.common.collect.ImmutableList.copyOf;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.lang.Integer.MAX_VALUE;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.lang.Integer.MIN_VALUE;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.time.Clock.systemUTC;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.time.Instant.MIN;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.time.ZoneOffset.SHORT_IDS;",
|
||||
"import static java.time.ZoneOffset.UTC;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.util.Collections.min;",
|
||||
"import static java.util.Locale.ENGLISH;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.util.Locale.ROOT;",
|
||||
"// BUG: Diagnostic contains:",
|
||||
"import static java.util.Optional.empty;",
|
||||
"import static pkg.A.WithMethodThatIsSelectivelyFlagged.list;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import java.time.Instant;",
|
||||
"import java.time.ZoneOffset;",
|
||||
"import java.util.Locale;",
|
||||
"import java.util.Map;",
|
||||
"import pkg.A.Wrapper.ZERO;",
|
||||
"",
|
||||
"class A {",
|
||||
" private Integer MIN_VALUE = 12;",
|
||||
"",
|
||||
" void m() {",
|
||||
" nullToEmpty(null);",
|
||||
" copyOf(ImmutableList.of());",
|
||||
" int max = MAX_VALUE;",
|
||||
" int min = MIN_VALUE;",
|
||||
" systemUTC();",
|
||||
" Instant minInstant = MIN;",
|
||||
" Map<String, String> shortIds = SHORT_IDS;",
|
||||
" ZoneOffset utc = UTC;",
|
||||
" min(ImmutableSet.of());",
|
||||
" Locale english = ENGLISH;",
|
||||
" Locale root = ROOT;",
|
||||
" empty();",
|
||||
"",
|
||||
" list();",
|
||||
" new ZERO();",
|
||||
" }",
|
||||
"",
|
||||
" static final class WithMethodThatIsSelectivelyFlagged {",
|
||||
" static ImmutableList<String> list() {",
|
||||
" return ImmutableList.of();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" static final class Wrapper {",
|
||||
" static final class ZERO {}",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(NonStaticImport.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import static com.google.common.collect.ImmutableList.copyOf;",
|
||||
"import static com.google.common.collect.ImmutableSet.of;",
|
||||
"import static java.time.Clock.systemUTC;",
|
||||
"import static java.time.Instant.MAX;",
|
||||
"import static java.time.Instant.MIN;",
|
||||
"import static java.util.Collections.min;",
|
||||
"import static java.util.Locale.ROOT;",
|
||||
"import static java.util.Optional.empty;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import java.time.Clock;",
|
||||
"import java.time.Instant;",
|
||||
"import java.util.Locale;",
|
||||
"import java.util.Optional;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" systemUTC();",
|
||||
" Clock.systemUTC();",
|
||||
"",
|
||||
" Optional<Integer> o1 = empty();",
|
||||
" Optional<Integer> o2 = Optional.empty();",
|
||||
"",
|
||||
" Object l1 = copyOf(ImmutableList.of());",
|
||||
" Object l2 = ImmutableList.copyOf(ImmutableList.of());",
|
||||
"",
|
||||
" Locale lo1 = ROOT;",
|
||||
" Locale lo2 = Locale.ROOT;",
|
||||
"",
|
||||
" Instant i1 = MIN;",
|
||||
" Instant i2 = MAX;",
|
||||
"",
|
||||
" ImmutableSet.of(min(of()));",
|
||||
" }",
|
||||
"",
|
||||
" private static final class WithCustomConstant {",
|
||||
" private static final Instant MIN = Instant.EPOCH;",
|
||||
" private static final Instant OTHER = MIN;",
|
||||
" private static final Instant OTHER_MAX = MAX;",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import java.time.Clock;",
|
||||
"import java.time.Instant;",
|
||||
"import java.util.Collections;",
|
||||
"import java.util.Locale;",
|
||||
"import java.util.Optional;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Clock.systemUTC();",
|
||||
" Clock.systemUTC();",
|
||||
"",
|
||||
" Optional<Integer> o1 = Optional.empty();",
|
||||
" Optional<Integer> o2 = Optional.empty();",
|
||||
"",
|
||||
" Object l1 = ImmutableList.copyOf(ImmutableList.of());",
|
||||
" Object l2 = ImmutableList.copyOf(ImmutableList.of());",
|
||||
"",
|
||||
" Locale lo1 = Locale.ROOT;",
|
||||
" Locale lo2 = Locale.ROOT;",
|
||||
"",
|
||||
" Instant i1 = Instant.MIN;",
|
||||
" Instant i2 = Instant.MAX;",
|
||||
"",
|
||||
" ImmutableSet.of(Collections.min(ImmutableSet.of()));",
|
||||
" }",
|
||||
"",
|
||||
" private static final class WithCustomConstant {",
|
||||
" private static final Instant MIN = Instant.EPOCH;",
|
||||
" private static final Instant OTHER = MIN;",
|
||||
" private static final Instant OTHER_MAX = Instant.MAX;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
final class ScheduledTransactionTraceTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.newrelic.api.agent.Trace;",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" void notScheduled() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Trace",
|
||||
" void scheduledButImproperlyTraced1() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Trace(dispatcher = false)",
|
||||
" void scheduledButImproperlyTraced2() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(dispatcher = true)",
|
||||
" void scheduledAndProperlyTraced() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithoutNewRelicAgentApiOnClasspath() {
|
||||
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
|
||||
.withClasspath(Scheduled.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import com.newrelic.api.agent.Trace;",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace",
|
||||
" void scheduledButImproperlyTraced1() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(dispatcher = false)",
|
||||
" void scheduledButImproperlyTraced2() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(leaf = true)",
|
||||
" void scheduledButImproperlyTraced3() {}",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import com.newrelic.api.agent.Trace;",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Trace(dispatcher = true)",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(dispatcher = true)",
|
||||
" void scheduledButImproperlyTraced1() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(dispatcher = true)",
|
||||
" void scheduledButImproperlyTraced2() {}",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" @Trace(dispatcher = true, leaf = true)",
|
||||
" void scheduledButImproperlyTraced3() {}",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -9,21 +9,24 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
final class StaticImportTest {
|
||||
@Test
|
||||
void candidateMethodsAreNotRedundant() {
|
||||
void candidateTypesDoNotClash() {
|
||||
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES)
|
||||
.doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void candidateMembersAreNotRedundant() {
|
||||
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet())
|
||||
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exemptedMembersAreNotVacuous() {
|
||||
assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.keySet())
|
||||
.isSubsetOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES);
|
||||
}
|
||||
void candidateMembersDoNotClash() {
|
||||
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.entries())
|
||||
.doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS.entries());
|
||||
|
||||
@Test
|
||||
void exemptedMembersAreNotRedundant() {
|
||||
assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.values())
|
||||
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_EXEMPTED_IDENTIFIERS);
|
||||
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.values())
|
||||
.doesNotContainAnyElementsOf(NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,6 +40,7 @@ final class StaticImportTest {
|
||||
"import static java.util.function.Predicate.not;",
|
||||
"import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;",
|
||||
"",
|
||||
"import com.fasterxml.jackson.annotation.JsonCreator;",
|
||||
"import com.google.common.base.Predicates;",
|
||||
"import com.google.common.collect.ImmutableMap;",
|
||||
"import com.google.common.collect.ImmutableMultiset;",
|
||||
@@ -103,6 +107,12 @@ final class StaticImportTest {
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @JsonCreator(mode = JsonCreator.Mode.DELEGATING)",
|
||||
" private static A jsonCreator(int a) {",
|
||||
" return new A();",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @UseImportPolicy(ImportPolicy.IMPORT_TOP_LEVEL)",
|
||||
" void refasterAfterTemplate() {}",
|
||||
"",
|
||||
@@ -118,6 +128,7 @@ final class StaticImportTest {
|
||||
"A.java",
|
||||
"import static java.util.function.Predicate.not;",
|
||||
"",
|
||||
"import com.fasterxml.jackson.annotation.JsonCreator;",
|
||||
"import com.google.common.base.Predicates;",
|
||||
"import com.google.common.collect.ImmutableMap;",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
@@ -174,6 +185,11 @@ final class StaticImportTest {
|
||||
" @DateTimeFormat(iso = ISO.DATE_TIME) String dateTime,",
|
||||
" @DateTimeFormat(iso = ISO.TIME) String time) {}",
|
||||
"",
|
||||
" @JsonCreator(mode = JsonCreator.Mode.DELEGATING)",
|
||||
" private static A jsonCreator(int a) {",
|
||||
" return new A();",
|
||||
" }",
|
||||
"",
|
||||
" @BugPattern(",
|
||||
" summary = \"\",",
|
||||
" linkType = BugPattern.LinkType.NONE,",
|
||||
@@ -186,6 +202,7 @@ final class StaticImportTest {
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import static com.fasterxml.jackson.annotation.JsonCreator.Mode.DELEGATING;",
|
||||
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
|
||||
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
|
||||
"import static com.google.errorprone.BugPattern.LinkType.NONE;",
|
||||
@@ -205,6 +222,7 @@ final class StaticImportTest {
|
||||
"import static org.springframework.http.MediaType.APPLICATION_XHTML_XML;",
|
||||
"import static org.springframework.http.MediaType.TEXT_HTML;",
|
||||
"",
|
||||
"import com.fasterxml.jackson.annotation.JsonCreator;",
|
||||
"import com.google.common.base.Predicates;",
|
||||
"import com.google.common.collect.ImmutableMap;",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
@@ -258,6 +276,11 @@ final class StaticImportTest {
|
||||
" @DateTimeFormat(iso = DATE_TIME) String dateTime,",
|
||||
" @DateTimeFormat(iso = TIME) String time) {}",
|
||||
"",
|
||||
" @JsonCreator(mode = DELEGATING)",
|
||||
" private static A jsonCreator(int a) {",
|
||||
" return new A();",
|
||||
" }",
|
||||
"",
|
||||
" @BugPattern(summary = \"\", linkType = NONE, severity = SUGGESTION, tags = SIMPLIFICATION)",
|
||||
" static final class TestBugPattern {}",
|
||||
"",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
@@ -12,8 +10,8 @@ final class StringJoinTest {
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(StringJoin.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"valueOf", containsPattern("Prefer `String#valueOf` over `String#format`"))
|
||||
.expectErrorMessage("join", containsPattern("Prefer `String#join` over `String#format`"))
|
||||
"valueOf", m -> m.contains("Prefer `String#valueOf` over `String#format`"))
|
||||
.expectErrorMessage("join", m -> m.contains("Prefer `String#join` over `String#format`"))
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.Formattable;",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class UnqualifiedSuggestedFixImportTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(UnqualifiedSuggestedFixImport.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"IMPORT",
|
||||
m ->
|
||||
m.contains(
|
||||
"Prefer `SuggestedFixes#qualifyType` over direct invocation of `SuggestedFix.Builder#addImport`"))
|
||||
.expectErrorMessage(
|
||||
"STATIC_IMPORT",
|
||||
m ->
|
||||
m.contains(
|
||||
"Prefer `SuggestedFixes#qualifyStaticImport` over direct invocation of `SuggestedFix.Builder#addStaticImport`"))
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.fixes.SuggestedFix;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" System.out.println(\"foo\");",
|
||||
" addImport(\"bar\");",
|
||||
" addStaticImport(\"baz\");",
|
||||
"",
|
||||
" SuggestedFix.Builder builder = SuggestedFix.builder();",
|
||||
" // BUG: Diagnostic matches: IMPORT",
|
||||
" builder.addImport(\"java.lang.String\");",
|
||||
" // BUG: Diagnostic matches: STATIC_IMPORT",
|
||||
" builder.addStaticImport(\"java.lang.String.toString\");",
|
||||
" builder.build();",
|
||||
" }",
|
||||
"",
|
||||
" private void addImport(String s) {}",
|
||||
"",
|
||||
" private void addStaticImport(String s) {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user