mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
176 Commits
v0.17.0
...
sschroever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
728814aa6a | ||
|
|
b3d391c80d | ||
|
|
43a1ea1d6c | ||
|
|
c4fd4871fc | ||
|
|
23bbb34fb7 | ||
|
|
56a82e56c0 | ||
|
|
8f0d870fff | ||
|
|
d531ceb9c6 | ||
|
|
dc87aeda6c | ||
|
|
1668546450 | ||
|
|
fc9c20062a | ||
|
|
27e9fe79a5 | ||
|
|
e37280b752 | ||
|
|
fff368c80a | ||
|
|
37cbee0f0a | ||
|
|
141822b614 | ||
|
|
b1bfc1fd36 | ||
|
|
13684ec59d | ||
|
|
a51ff4de4e | ||
|
|
da9b313ff7 | ||
|
|
6a50fe2d9c | ||
|
|
ed32cbae06 | ||
|
|
00549a3ba6 | ||
|
|
13f1fa3167 | ||
|
|
6396def588 | ||
|
|
7599b0f22f | ||
|
|
08eb7e7699 | ||
|
|
89f918c23e | ||
|
|
f08fc344f5 | ||
|
|
99aa656a1e | ||
|
|
86fbaf7403 | ||
|
|
563012549a | ||
|
|
4f46eb30d2 | ||
|
|
2f30082127 | ||
|
|
548506fbbb | ||
|
|
bdef83bce5 | ||
|
|
f7f665681d | ||
|
|
7c0d544cf8 | ||
|
|
9390b6f571 | ||
|
|
176a833d89 | ||
|
|
3cacd27248 | ||
|
|
f06a2e4d43 | ||
|
|
cd06288f5b | ||
|
|
4b458e01bd | ||
|
|
eccfc34c78 | ||
|
|
0317cb73d6 | ||
|
|
ce6931cc36 | ||
|
|
9940576ea8 | ||
|
|
caf2a86922 | ||
|
|
507d759d02 | ||
|
|
8ec4936980 | ||
|
|
0f0b27abb7 | ||
|
|
2b1dbd98cd | ||
|
|
e0583a8f0a | ||
|
|
21437a43aa | ||
|
|
cf8af8c5cf | ||
|
|
07bd4b0b54 | ||
|
|
d1765fea0e | ||
|
|
5922c5b032 | ||
|
|
56b60f5cf6 | ||
|
|
be6b17b7dc | ||
|
|
f782ec2d8f | ||
|
|
6e88e3cea8 | ||
|
|
09fb21358a | ||
|
|
c27a626042 | ||
|
|
da33e800fa | ||
|
|
bc13976cc7 | ||
|
|
7f1f0656fd | ||
|
|
4e99382f42 | ||
|
|
9aa03aa842 | ||
|
|
71cc09a7e7 | ||
|
|
9b4cbfb84d | ||
|
|
f2238c779c | ||
|
|
6e893e9869 | ||
|
|
7ee8826e96 | ||
|
|
a0689f62b4 | ||
|
|
1b00c87b2f | ||
|
|
0bce1a0e29 | ||
|
|
ce18b0c058 | ||
|
|
4f2c143b9c | ||
|
|
f4740e0e64 | ||
|
|
3a155169ef | ||
|
|
13847f6d2c | ||
|
|
6a81b92e11 | ||
|
|
5794b404f2 | ||
|
|
b2d7ed4dc7 | ||
|
|
0fdc102aa5 | ||
|
|
ce3cf6a2d0 | ||
|
|
0f657f8835 | ||
|
|
5f56f43c40 | ||
|
|
ef6faf518a | ||
|
|
aa08d954a0 | ||
|
|
e4d1818ed0 | ||
|
|
beb96f0f4b | ||
|
|
4d1eeb2be1 | ||
|
|
9cbc6f875c | ||
|
|
5583630fb4 | ||
|
|
91e841ce12 | ||
|
|
3956a82cd5 | ||
|
|
c57debdc25 | ||
|
|
6a13efded8 | ||
|
|
432cf4560d | ||
|
|
966fb36ac9 | ||
|
|
c63d9350d2 | ||
|
|
e74874b04c | ||
|
|
f821d3775b | ||
|
|
3d5ee10d93 | ||
|
|
26da67d1f5 | ||
|
|
b3ca01a6c7 | ||
|
|
9f222e9efe | ||
|
|
097af51a3e | ||
|
|
c62e6c1127 | ||
|
|
5960423c4e | ||
|
|
188715c3c0 | ||
|
|
fb45bb00ed | ||
|
|
fd5fc913ce | ||
|
|
ae20c6069d | ||
|
|
8457bd5026 | ||
|
|
fcfb97b0e0 | ||
|
|
15680b4cb3 | ||
|
|
afebfbf478 | ||
|
|
fe2ac938f3 | ||
|
|
078d8c16fa | ||
|
|
c679a3fc0c | ||
|
|
de54b4bf64 | ||
|
|
ea60241782 | ||
|
|
e4f928addb | ||
|
|
059cc9e2db | ||
|
|
1e43c28c95 | ||
|
|
a07e9b3115 | ||
|
|
aec38d2e33 | ||
|
|
5b77663288 | ||
|
|
97c2bbd4b1 | ||
|
|
9c8fbfd36a | ||
|
|
38e6c3fdb5 | ||
|
|
5b1d82cfeb | ||
|
|
0821a95fcc | ||
|
|
f4afe457cb | ||
|
|
fd56ca8b6e | ||
|
|
882794d63b | ||
|
|
73ed6e32c7 | ||
|
|
ca5c3dd3b4 | ||
|
|
f6e9dbb996 | ||
|
|
260021c961 | ||
|
|
ec54e79f3e | ||
|
|
4cbfdba521 | ||
|
|
d94f65a1f0 | ||
|
|
9afdf2ddf3 | ||
|
|
060c901479 | ||
|
|
1feee4f64a | ||
|
|
552ddf6a7d | ||
|
|
5d92c6c6ce | ||
|
|
fa8ca80040 | ||
|
|
b733179cd0 | ||
|
|
3d9aab7c5b | ||
|
|
366cdda3d8 | ||
|
|
5b6dd147ef | ||
|
|
a868b03130 | ||
|
|
fdf9bb5d25 | ||
|
|
363b0c22c7 | ||
|
|
32ec35a354 | ||
|
|
635fe280f8 | ||
|
|
aac9b6bf10 | ||
|
|
c322ea1bbc | ||
|
|
a433a90673 | ||
|
|
5a37d65632 | ||
|
|
77d183f8fd | ||
|
|
2eb4e853c5 | ||
|
|
45a7242cf5 | ||
|
|
c85070ba23 | ||
|
|
a687f09bf0 | ||
|
|
2e4fdcb0db | ||
|
|
1005d93b7e | ||
|
|
136123f6b4 | ||
|
|
4cb5f0079d | ||
|
|
290ddf1972 |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -42,9 +42,9 @@ 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.10`).
|
||||
- Error Prone version (e.g. `2.25.0`).
|
||||
- Error Prone Support version (e.g. `0.15.0`).
|
||||
- Java version (i.e. `java --version`, e.g. `17.0.13`).
|
||||
- Error Prone version (e.g. `2.35.1`).
|
||||
- Error Prone Support version (e.g. `0.19.0`).
|
||||
|
||||
### Additional context
|
||||
|
||||
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -9,24 +9,24 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 17.0.10, 21.0.2 ]
|
||||
os: [ ubuntu-24.04 ]
|
||||
jdk: [ 17.0.13, 21.0.5, 23.0.1 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-14
|
||||
jdk: 17.0.10
|
||||
jdk: 17.0.13
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.10
|
||||
jdk: 17.0.13
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
@@ -42,11 +42,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 and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
java-distribution: ${{ matrix.distribution }}
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
- name: Build project against vanilla Error Prone, compile Javadoc
|
||||
@@ -54,6 +54,6 @@ jobs:
|
||||
- name: Build project with self-check against Error Prone fork
|
||||
run: mvn -T1C clean verify -Perror-prone-fork -Pnon-maven-central -Pself-check -s settings.xml
|
||||
- name: Remove installed project artifacts
|
||||
run: mvn build-helper:remove-project-artifact
|
||||
run: mvn dependency:purge-local-repository -DmanualInclude='${project.groupId}' -DresolutionFuzziness=groupId
|
||||
|
||||
# XXX: Enable Codecov once we "go public".
|
||||
|
||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@@ -19,10 +19,10 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
@@ -34,11 +34,11 @@ jobs:
|
||||
repo.maven.apache.org:443
|
||||
uploads.github.com:443
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
java-version: 17.0.10
|
||||
java-version: 17.0.13
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
|
||||
with:
|
||||
|
||||
12
.github/workflows/deploy-website.yml
vendored
12
.github/workflows/deploy-website.yml
vendored
@@ -9,10 +9,10 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
@@ -39,10 +39,10 @@ jobs:
|
||||
www.youtube.com:443
|
||||
youtrack.jetbrains.com:443
|
||||
- name: Check out code
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0
|
||||
- uses: ruby/setup-ruby@7d3497fd78c07c0d84ebafa58d8dac60cd1f0763 # v1.199.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
@@ -68,13 +68,13 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
|
||||
15
.github/workflows/openssf-scorecard.yml
vendored
15
.github/workflows/openssf-scorecard.yml
vendored
@@ -18,30 +18,31 @@ jobs:
|
||||
contents: read
|
||||
security-events: write
|
||||
id-token: write
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.deps.dev:443
|
||||
api.github.com:443
|
||||
api.osv.dev:443
|
||||
api.scorecard.dev:443
|
||||
api.securityscorecards.dev:443
|
||||
fulcio.sigstore.dev:443
|
||||
github.com:443
|
||||
index.docker.io:443
|
||||
oss-fuzz-build-logs.storage.googleapis.com:443
|
||||
rekor.sigstore.dev:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
repo.maven.apache.org:443
|
||||
*.sigstore.dev:443
|
||||
www.bestpractices.dev:443
|
||||
- name: Check out code
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run OpenSSF Scorecard analysis
|
||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
12
.github/workflows/pitest-analyze-pr.yml
vendored
12
.github/workflows/pitest-analyze-pr.yml
vendored
@@ -9,10 +9,10 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
analyze-pr:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
objects.githubusercontent.com:443
|
||||
repo.maven.apache.org:443
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
checkout-fetch-depth: 2
|
||||
java-version: 17.0.10
|
||||
java-version: 17.0.13
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Run Pitest
|
||||
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
|
||||
# analyzes lines changed in the associated pull request, as GitHub
|
||||
@@ -38,7 +38,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@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: pitest-reports
|
||||
path: ./target/pit-reports-ci
|
||||
|
||||
10
.github/workflows/pitest-update-pr.yml
vendored
10
.github/workflows/pitest-update-pr.yml
vendored
@@ -17,10 +17,10 @@ jobs:
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
@@ -31,11 +31,11 @@ jobs:
|
||||
objects.githubusercontent.com:443
|
||||
repo.maven.apache.org:443
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
java-version: 17.0.10
|
||||
java-version: 17.0.13
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Download Pitest analysis artifact
|
||||
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
|
||||
with:
|
||||
|
||||
30
.github/workflows/run-integration-tests.yml
vendored
30
.github/workflows/run-integration-tests.yml
vendored
@@ -1,9 +1,9 @@
|
||||
# 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:
|
||||
# XXX: Review whether then build matrix should also vary JDK or OS versions.
|
||||
# XXX: Support `/integration-test [name...]` comment syntax to specify the
|
||||
# subset of integration tests to 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:
|
||||
@@ -16,16 +16,20 @@ jobs:
|
||||
name: On-demand integration test
|
||||
if: |
|
||||
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
integration-test: [ "checkstyle", "metrics" ]
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
checkstyle.org:443
|
||||
example.com:80
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
oss.sonatype.org:443
|
||||
@@ -33,21 +37,21 @@ jobs:
|
||||
repo.maven.apache.org:443
|
||||
repository.sonatype.org:443
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
|
||||
java-version: 17.0.10
|
||||
java-version: 17.0.13
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Install project to local Maven repository
|
||||
run: mvn -T1C install -DskipTests -Dverification.skip
|
||||
- name: Run integration test
|
||||
run: xvfb-run ./integration-tests/checkstyle.sh "${{ runner.temp }}/artifacts"
|
||||
run: xvfb-run "./integration-tests/${{ matrix.integration-test }}.sh" "${{ runner.temp }}/artifacts"
|
||||
- name: Upload artifacts on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||
with:
|
||||
name: integration-test-checkstyle
|
||||
name: "integration-test-${{ matrix.integration-test }}"
|
||||
path: "${{ runner.temp }}/artifacts"
|
||||
- name: Remove installed project artifacts
|
||||
run: mvn build-helper:remove-project-artifact
|
||||
run: mvn dependency:purge-local-repository -DmanualInclude='${project.groupId}' -DresolutionFuzziness=groupId
|
||||
|
||||
15
.github/workflows/sonarcloud.yml
vendored
15
.github/workflows/sonarcloud.yml
vendored
@@ -16,30 +16,31 @@ jobs:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
analysis-sensorcache-eu-central-1-prod.s3.amazonaws.com:443
|
||||
api.adoptium.net:443
|
||||
api.sonarcloud.io:443
|
||||
api.nuget.org:443
|
||||
ea6ne4j2sb.execute-api.eu-central-1.amazonaws.com:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
repo.maven.apache.org:443
|
||||
sc-cleancode-sensorcache-eu-central-1-prod.s3.amazonaws.com:443
|
||||
scanner.sonarcloud.io:443
|
||||
*.sonarcloud.io:443
|
||||
sonarcloud.io:443
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0
|
||||
with:
|
||||
checkout-fetch-depth: 0
|
||||
java-version: 17.0.10
|
||||
java-version: 17.0.13
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Create missing `test` directory
|
||||
# XXX: Drop this step in favour of actually having a test.
|
||||
run: mkdir refaster-compiler/src/test
|
||||
|
||||
@@ -3,11 +3,24 @@
|
||||
"extends": [
|
||||
"helpers:pinGitHubActionDigests"
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": [
|
||||
"^integration-tests/.*(-init\\.patch|\\.sh)$"
|
||||
],
|
||||
"matchStrings": [
|
||||
"\\b(?<packageName>[a-z0-9_.-]+?:[a-z0-9_.-]+?):(?<currentValue>[^:]+?):[a-zA-Z0-9_-]+\\b",
|
||||
"<version>(?<currentValue>.*?)<!-- Renovate: (?<packageName>.*?) --></version>"
|
||||
],
|
||||
"datasourceTemplate": "maven"
|
||||
}
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": [
|
||||
"^org\\.springframework:spring-framework-bom$",
|
||||
"^org\\.springframework\\.boot:spring-boot[a-z-]*$"
|
||||
"matchPackageNames": [
|
||||
"/^org\\.springframework:spring-framework-bom$/",
|
||||
"/^org\\.springframework\\.boot:spring-boot[a-z-]*$/"
|
||||
],
|
||||
"separateMinorPatch": true
|
||||
},
|
||||
|
||||
@@ -302,7 +302,7 @@ channel; please see our [security policy][security] for details.
|
||||
[refaster]: https://errorprone.info/docs/refaster
|
||||
[refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java
|
||||
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
|
||||
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
|
||||
[reproducible-builds-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/tech/picnic/error-prone-support/error-prone-support/badge.json
|
||||
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
|
||||
[script-apply-error-prone-suggestions]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/apply-error-prone-suggestions.sh
|
||||
[script-run-branch-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-branch-mutation-tests.sh
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>documentation-support</artifactId>
|
||||
@@ -33,6 +33,15 @@
|
||||
<artifactId>error_prone_test_helpers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from classes that test a {@code
|
||||
@@ -40,7 +40,7 @@ import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
public final class BugPatternTestExtractor implements Extractor<BugPatternTestCases> {
|
||||
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
|
||||
public BugPatternTestExtractor() {}
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
public Optional<BugPatternTestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
BugPatternTestCollector collector = new BugPatternTestCollector();
|
||||
|
||||
collector.scan(tree, state);
|
||||
@@ -59,7 +59,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
.filter(not(ImmutableList::isEmpty))
|
||||
.map(
|
||||
tests ->
|
||||
new AutoValue_BugPatternTestExtractor_TestCases(
|
||||
new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
|
||||
state.getPath().getCompilationUnit().getSourceFile().toUri(),
|
||||
ASTHelpers.getSymbol(tree).className(),
|
||||
tests));
|
||||
@@ -95,10 +95,10 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.namedAnyOf("addOutputLines", "expectUnchanged");
|
||||
|
||||
private final List<TestCase> collectedTestCases = new ArrayList<>();
|
||||
private final List<BugPatternTestCase> collectedBugPatternTestCases = new ArrayList<>();
|
||||
|
||||
private ImmutableList<TestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedTestCases);
|
||||
private ImmutableList<BugPatternTestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedBugPatternTestCases);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,14 +110,14 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
classUnderTest -> {
|
||||
List<TestEntry> entries = new ArrayList<>();
|
||||
if (isReplacementTest) {
|
||||
extractReplacementTestCases(node, entries, state);
|
||||
extractReplacementBugPatternTestCases(node, entries, state);
|
||||
} else {
|
||||
extractIdentificationTestCases(node, entries, state);
|
||||
extractIdentificationBugPatternTestCases(node, entries, state);
|
||||
}
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
collectedTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_TestCase(
|
||||
collectedBugPatternTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_BugPatternTestCase(
|
||||
classUnderTest, ImmutableList.copyOf(entries).reverse()));
|
||||
}
|
||||
});
|
||||
@@ -140,7 +140,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private static void extractIdentificationTestCases(
|
||||
private static void extractIdentificationBugPatternTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) {
|
||||
String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
|
||||
@@ -155,11 +155,11 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractIdentificationTestCases(methodInvocation, sink, state);
|
||||
extractIdentificationBugPatternTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractReplacementTestCases(
|
||||
private static void extractReplacementBugPatternTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) {
|
||||
/*
|
||||
@@ -185,7 +185,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractReplacementTestCases(methodInvocation, sink, state);
|
||||
extractReplacementBugPatternTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,24 +208,26 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCases.class)
|
||||
abstract static class TestCases {
|
||||
static TestCases create(URI source, String testClass, ImmutableList<TestCase> testCases) {
|
||||
return new AutoValue_BugPatternTestExtractor_TestCases(source, testClass, testCases);
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCases.class)
|
||||
abstract static class BugPatternTestCases {
|
||||
static BugPatternTestCases create(
|
||||
URI source, String testClass, ImmutableList<BugPatternTestCase> testCases) {
|
||||
return new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
|
||||
source, testClass, testCases);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String testClass();
|
||||
|
||||
abstract ImmutableList<TestCase> testCases();
|
||||
abstract ImmutableList<BugPatternTestCase> testCases();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCase.class)
|
||||
abstract static class TestCase {
|
||||
static TestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
|
||||
return new AutoValue_BugPatternTestExtractor_TestCase(classUnderTest, entries);
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCase.class)
|
||||
abstract static class BugPatternTestCase {
|
||||
static BugPatternTestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
|
||||
return new AutoValue_BugPatternTestExtractor_BugPatternTestCase(classUnderTest, entries);
|
||||
}
|
||||
|
||||
abstract String classUnderTest();
|
||||
|
||||
@@ -14,7 +14,6 @@ import java.io.IOException;
|
||||
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;
|
||||
|
||||
@@ -87,6 +86,6 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
}
|
||||
|
||||
private static String getSimpleClassName(URI path) {
|
||||
return Paths.get(path).getFileName().toString().replace(".java", "");
|
||||
return Path.of(path).getFileName().toString().replace(".java", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.FormatMethod;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from Refaster rule input and output test
|
||||
* classes.
|
||||
*/
|
||||
// XXX: Drop this extractor if/when the Refaster test framework is reimplemented such that tests can
|
||||
// be located alongside rules, rather than in two additional resource files as currently required by
|
||||
// `RefasterRuleCollection`.
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class RefasterRuleCollectionTestExtractor implements Extractor<RefasterTestCases> {
|
||||
private static final Matcher<ClassTree> IS_REFASTER_RULE_COLLECTION_TEST_CASE =
|
||||
isSubtypeOf("tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase");
|
||||
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
|
||||
private static final Pattern TEST_CLASS_FILE_NAME_PATTERN =
|
||||
Pattern.compile(".*(Input|Output)\\.java");
|
||||
private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("test(.*)");
|
||||
private static final String LINE_SEPARATOR = "\n";
|
||||
private static final Splitter LINE_SPLITTER = Splitter.on(LINE_SEPARATOR);
|
||||
|
||||
/** Instantiates a new {@link RefasterRuleCollectionTestExtractor} instance. */
|
||||
public RefasterRuleCollectionTestExtractor() {}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "refaster-rule-collection-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RefasterTestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
if (!IS_REFASTER_RULE_COLLECTION_TEST_CASE.matches(tree, state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
URI sourceFile = state.getPath().getCompilationUnit().getSourceFile().toUri();
|
||||
return Optional.of(
|
||||
RefasterTestCases.create(
|
||||
sourceFile,
|
||||
getRuleCollectionName(tree),
|
||||
isInputFile(sourceFile),
|
||||
getRefasterTestCases(tree, state)));
|
||||
}
|
||||
|
||||
private static String getRuleCollectionName(ClassTree tree) {
|
||||
String className = tree.getSimpleName().toString();
|
||||
|
||||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
|
||||
// aspects of `RefasterRuleCollectionTestCase` subtypes.
|
||||
return tryExtractPatternGroup(className, TEST_CLASS_NAME_PATTERN)
|
||||
.orElseThrow(
|
||||
violation(
|
||||
"Refaster rule collection test class name '%s' does not match '%s'",
|
||||
className, TEST_CLASS_NAME_PATTERN));
|
||||
}
|
||||
|
||||
private static boolean isInputFile(URI sourceFile) {
|
||||
String path = sourceFile.getPath();
|
||||
|
||||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
|
||||
// aspects of `RefasterRuleCollectionTestCase` subtypes.
|
||||
return "Input"
|
||||
.equals(
|
||||
tryExtractPatternGroup(path, TEST_CLASS_FILE_NAME_PATTERN)
|
||||
.orElseThrow(
|
||||
violation(
|
||||
"Refaster rule collection test file name '%s' does not match '%s'",
|
||||
path, TEST_CLASS_FILE_NAME_PATTERN)));
|
||||
}
|
||||
|
||||
private static ImmutableList<RefasterTestCase> getRefasterTestCases(
|
||||
ClassTree tree, VisitorState state) {
|
||||
return tree.getMembers().stream()
|
||||
.filter(MethodTree.class::isInstance)
|
||||
.map(MethodTree.class::cast)
|
||||
.flatMap(m -> tryExtractRefasterTestCase(m, state).stream())
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static Optional<RefasterTestCase> tryExtractRefasterTestCase(
|
||||
MethodTree method, VisitorState state) {
|
||||
return tryExtractPatternGroup(method.getName().toString(), TEST_METHOD_NAME_PATTERN)
|
||||
.map(name -> RefasterTestCase.create(name, getFormattedSource(method, state)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source code for the specified method.
|
||||
*
|
||||
* @implNote This operation attempts to trim leading whitespace, such that the start and end of
|
||||
* the method declaration are aligned. The implemented heuristic assumes that the code is
|
||||
* formatted using Google Java Format.
|
||||
*/
|
||||
// XXX: Leading Javadoc and other comments are currently not extracted. Consider fixing this.
|
||||
private static String getFormattedSource(MethodTree method, VisitorState state) {
|
||||
String source = SourceCode.treeToString(method, state);
|
||||
int finalNewline = source.lastIndexOf(LINE_SEPARATOR);
|
||||
if (finalNewline < 0) {
|
||||
return source;
|
||||
}
|
||||
|
||||
int indentation = Math.max(0, source.lastIndexOf(' ') - finalNewline);
|
||||
String prefixToStrip = " ".repeat(indentation);
|
||||
|
||||
return LINE_SPLITTER
|
||||
.splitToStream(source)
|
||||
.map(line -> line.startsWith(prefixToStrip) ? line.substring(indentation) : line)
|
||||
.collect(joining(LINE_SEPARATOR));
|
||||
}
|
||||
|
||||
private static Optional<String> tryExtractPatternGroup(String input, Pattern pattern) {
|
||||
java.util.regex.Matcher matcher = pattern.matcher(input);
|
||||
return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty();
|
||||
}
|
||||
|
||||
@FormatMethod
|
||||
private static Supplier<VerifyException> violation(String format, Object... args) {
|
||||
return () -> new VerifyException(String.format(format, args));
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases.class)
|
||||
abstract static class RefasterTestCases {
|
||||
static RefasterTestCases create(
|
||||
URI source,
|
||||
String ruleCollection,
|
||||
boolean isInput,
|
||||
ImmutableList<RefasterTestCase> testCases) {
|
||||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases(
|
||||
source, ruleCollection, isInput, testCases);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String ruleCollection();
|
||||
|
||||
abstract boolean isInput();
|
||||
|
||||
abstract ImmutableList<RefasterTestCase> testCases();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase.class)
|
||||
abstract static class RefasterTestCase {
|
||||
static RefasterTestCase create(String name, String content) {
|
||||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase(name, content);
|
||||
}
|
||||
|
||||
abstract String name();
|
||||
|
||||
abstract String content();
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCase;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.IdentificationTestEntry;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.ReplacementTestEntry;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCase;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
|
||||
final class BugPatternTestExtractorTest {
|
||||
@Test
|
||||
@@ -269,11 +269,11 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///SingleFileCompilationTestHelperTest.java"),
|
||||
"SingleFileCompilationTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"SingleFileCompilationTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
@@ -302,11 +302,11 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///SingleFileCompilationTestHelperWithSetArgsTest.java"),
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
@@ -335,11 +335,11 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"MultiFileCompilationTestHelperTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///MultiFileCompilationTestHelperTest.java"),
|
||||
"MultiFileCompilationTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"MultiFileCompilationTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
@@ -370,11 +370,11 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///SingleFileBugCheckerRefactoringTestHelperTest.java"),
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
@@ -408,12 +408,12 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create(
|
||||
"file:///SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java"),
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
@@ -444,11 +444,11 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///MultiFileBugCheckerRefactoringTestHelperTest.java"),
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
@@ -484,16 +484,16 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create("file:///CompilationAndBugCheckerRefactoringTestHelpersTest.java"),
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
@@ -532,17 +532,17 @@ final class BugPatternTestExtractorTest {
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
|
||||
TestCases.create(
|
||||
BugPatternTestCases.create(
|
||||
URI.create(
|
||||
"file:///CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java"),
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
|
||||
TestCase.create(
|
||||
BugPatternTestCase.create(
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker2",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
@@ -550,9 +550,9 @@ final class BugPatternTestExtractorTest {
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(
|
||||
Path outputDirectory, String testClass, TestCases expected) {
|
||||
Path outputDirectory, String testClass, BugPatternTestCases expected) {
|
||||
assertThat(outputDirectory.resolve(String.format("bugpattern-test-%s.json", testClass)))
|
||||
.exists()
|
||||
.returns(expected, path -> Json.read(path, TestCases.class));
|
||||
.returns(expected, path -> Json.read(path, BugPatternTestCases.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ 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;
|
||||
@@ -41,7 +40,8 @@ final class DocumentationGeneratorTaskListenerTest {
|
||||
entry ->
|
||||
AclEntry.newBuilder(entry)
|
||||
.setPermissions(
|
||||
Sets.difference(entry.permissions(), ImmutableSet.of(ADD_SUBDIRECTORY)))
|
||||
Sets.difference(
|
||||
entry.permissions(), Sets.immutableEnumSet(ADD_SUBDIRECTORY)))
|
||||
.build())
|
||||
.collect(toImmutableList()));
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
|
||||
|
||||
final class RefasterRuleCollectionTestExtractorTest {
|
||||
@Test
|
||||
void noRefasterRuleTest(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory, "NoRefasterRuleTest.java", "public final class NoRefasterRuleTest {}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidTestClassName(@TempDir Path outputDirectory) {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"InvalidTestClassNameInput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class InvalidTestClassName implements RefasterRuleCollectionTestCase {}"))
|
||||
.cause()
|
||||
.isInstanceOf(VerifyException.class)
|
||||
.hasMessage(
|
||||
"Refaster rule collection test class name 'InvalidTestClassName' does not match '(.*)Test'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidFileName(@TempDir Path outputDirectory) {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"InvalidFileNameTest.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class InvalidFileNameTest implements RefasterRuleCollectionTestCase {}"))
|
||||
.cause()
|
||||
.isInstanceOf(VerifyException.class)
|
||||
.hasMessage(
|
||||
"Refaster rule collection test file name '/InvalidFileNameTest.java' does not match '.*(Input|Output)\\.java'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyRefasterRuleCollectionTestInput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"EmptyRefasterRuleCollectionTestInput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class EmptyRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"EmptyRefasterRuleCollectionTestInput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///EmptyRefasterRuleCollectionTestInput.java"),
|
||||
"EmptyRefasterRuleCollection",
|
||||
/* isInput= */ true,
|
||||
ImmutableList.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singletonRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingletonRefasterRuleCollectionTestOutput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class SingletonRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
|
||||
" int testMyRule() {",
|
||||
" return 42;",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingletonRefasterRuleCollectionTestOutput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///SingletonRefasterRuleCollectionTestOutput.java"),
|
||||
"SingletonRefasterRuleCollection",
|
||||
/* isInput= */ false,
|
||||
ImmutableList.of(
|
||||
RefasterTestCase.create(
|
||||
"MyRule",
|
||||
"""
|
||||
int testMyRule() {
|
||||
return 42;
|
||||
}"""))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void complexRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"pkg/ComplexRefasterRuleCollectionTestInput.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class ComplexRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
|
||||
" private static final String IGNORED_CONSTANT = \"constant\";",
|
||||
"",
|
||||
" @Override",
|
||||
" public ImmutableSet<Object> elidedTypesAndStaticImports() {",
|
||||
" return ImmutableSet.of();",
|
||||
" }",
|
||||
"",
|
||||
" /** Javadoc. */",
|
||||
" String testFirstRule() {",
|
||||
" return \"Don't panic\";",
|
||||
" }",
|
||||
"",
|
||||
" // Comment.",
|
||||
" String testSecondRule() {",
|
||||
" return \"Carry a towel\";",
|
||||
" }",
|
||||
"",
|
||||
" void testEmptyRule() {}",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"ComplexRefasterRuleCollectionTestInput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///pkg/ComplexRefasterRuleCollectionTestInput.java"),
|
||||
"ComplexRefasterRuleCollection",
|
||||
/* isInput= */ true,
|
||||
ImmutableList.of(
|
||||
RefasterTestCase.create(
|
||||
"FirstRule",
|
||||
"""
|
||||
String testFirstRule() {
|
||||
return "Don't panic";
|
||||
}"""),
|
||||
RefasterTestCase.create(
|
||||
"SecondRule",
|
||||
"""
|
||||
String testSecondRule() {
|
||||
return "Carry a towel";
|
||||
}"""),
|
||||
RefasterTestCase.create("EmptyRule", "void testEmptyRule() {}"))));
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(
|
||||
Path outputDirectory, String testIdentifier, RefasterTestCases expected) {
|
||||
assertThat(
|
||||
outputDirectory.resolve(
|
||||
String.format("refaster-rule-collection-test-%s.json", testIdentifier)))
|
||||
.exists()
|
||||
.returns(expected, path -> Json.read(path, RefasterTestCases.class));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -55,7 +55,12 @@
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<scope>provided</scope>
|
||||
<!-- XXX: One would expect this to be a `provided` dependency (as
|
||||
Refaster rules are interpreted by the `refaster-runner` module),
|
||||
but the `OptionalOrElseGet` bug checker defined by this module
|
||||
depends on the `RequiresComputation` matcher that
|
||||
`refaster-support` primarily exposes for use by Refaster rules.
|
||||
Review this setup. (Should the matchers be moved elsewhere?) -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
@@ -67,6 +72,11 @@
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
@@ -82,6 +92,11 @@
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
@@ -196,7 +211,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-11</artifactId>
|
||||
<artifactId>rewrite-java-17</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -287,6 +302,55 @@
|
||||
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- The Refaster input/output test classes used by
|
||||
`RefasterRuleCollection` are modelled as classpath
|
||||
resources, and thus not subject to the default test
|
||||
compilation step. These two custom compilation steps
|
||||
serve two purposes:
|
||||
- To provide early feedback in case of syntax errors.
|
||||
- To enable the `DocumentationGenerator` compiler
|
||||
plugin to extract documentation metadata from them.
|
||||
Note that the input and output files must be compiled
|
||||
separately and to distinct output directories, as they
|
||||
define the same set of class names. -->
|
||||
<!-- XXX: Drop these executions if/when the Refaster
|
||||
test framework is reimplemented such that tests can be
|
||||
located alongside rules, rather than in two additional
|
||||
resource files. -->
|
||||
<execution>
|
||||
<id>compile-refaster-test-input</id>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<phase>process-test-resources</phase>
|
||||
<configuration>
|
||||
<compileSourceRoots>
|
||||
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
|
||||
</compileSourceRoots>
|
||||
<testIncludes>
|
||||
<testInclude>**/*Input.java</testInclude>
|
||||
</testIncludes>
|
||||
<outputDirectory>${project.build.directory}/refaster-test-input</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>compile-refaster-test-output</id>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<phase>process-test-resources</phase>
|
||||
<configuration>
|
||||
<compileSourceRoots>
|
||||
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
|
||||
</compileSourceRoots>
|
||||
<testIncludes>
|
||||
<testInclude>**/*Output.java</testInclude>
|
||||
</testIncludes>
|
||||
<outputDirectory>${project.build.directory}/refaster-test-output</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
@@ -3,6 +3,7 @@ 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 java.util.function.Predicate.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
@@ -19,7 +20,6 @@ import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
@@ -38,12 +38,11 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
|
||||
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
|
||||
FIX_FACTORIES =
|
||||
ImmutableSet.of(
|
||||
CanonicalAnnotationSyntax::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntax::dropRedundantCurlies);
|
||||
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Fix>> FIX_FACTORIES =
|
||||
ImmutableSet.of(
|
||||
CanonicalAnnotationSyntax::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntax::dropRedundantCurlies);
|
||||
|
||||
/** Instantiates a new {@link CanonicalAnnotationSyntax} instance. */
|
||||
public CanonicalAnnotationSyntax() {}
|
||||
@@ -52,39 +51,38 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return FIX_FACTORIES.stream()
|
||||
.map(op -> op.apply(tree, state))
|
||||
.flatMap(Optional::stream)
|
||||
.filter(not(Fix::isEmpty))
|
||||
.findFirst()
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
||||
private static Fix dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
||||
if (!tree.getArguments().isEmpty()) {
|
||||
/* Parentheses are necessary. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
String src = state.getSourceForNode(tree);
|
||||
if (src == null) {
|
||||
/* Without the source code there's not much we can do. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
int parenIndex = src.indexOf('(');
|
||||
if (parenIndex < 0) {
|
||||
/* There are no redundant parentheses. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
return Optional.of(SuggestedFix.replace(tree, src.substring(0, parenIndex)));
|
||||
return SuggestedFix.replace(tree, src.substring(0, parenIndex));
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantValueAttribute(
|
||||
AnnotationTree tree, VisitorState state) {
|
||||
private static Fix dropRedundantValueAttribute(AnnotationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> args = tree.getArguments();
|
||||
if (args.size() != 1) {
|
||||
/* The `value` attribute, if specified, cannot be dropped. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
ExpressionTree arg = args.get(0);
|
||||
@@ -93,25 +91,23 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
* The annotation argument doesn't have a source representation, e.g. because `value` isn't
|
||||
* assigned explicitly.
|
||||
*/
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
ExpressionTree expr = AnnotationMatcherUtils.getArgument(tree, "value");
|
||||
if (expr == null) {
|
||||
/* This is not an explicit assignment to the `value` attribute. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
/* Replace the assignment with (the simplified representation of) just its value. */
|
||||
return Optional.of(
|
||||
SuggestedFix.replace(
|
||||
arg,
|
||||
simplifyAttributeValue(expr, state)
|
||||
.orElseGet(() -> SourceCode.treeToString(expr, state))));
|
||||
return SuggestedFix.replace(
|
||||
arg,
|
||||
simplifyAttributeValue(expr, state).orElseGet(() -> SourceCode.treeToString(expr, state)));
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
private static Fix dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
for (ExpressionTree arg : tree.getArguments()) {
|
||||
/*
|
||||
* We'll try to simplify each assignment's RHS; for non-assignment we'll try to simplify
|
||||
@@ -122,10 +118,10 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
|
||||
/* Store a fix for each expression that was successfully simplified. */
|
||||
simplifyAttributeValue(value, state)
|
||||
.ifPresent(expr -> fixes.add(SuggestedFix.builder().replace(value, expr)));
|
||||
.ifPresent(expr -> fix.merge(SuggestedFix.replace(value, expr)));
|
||||
}
|
||||
|
||||
return fixes.stream().reduce(SuggestedFix.Builder::merge).map(SuggestedFix.Builder::build);
|
||||
return fix.build();
|
||||
}
|
||||
|
||||
private static Optional<String> simplifyAttributeValue(ExpressionTree expr, VisitorState state) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.TypeCastTree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
|
||||
* of the form {@code T.class::cast}.
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check of the
|
||||
// `error-prone-experimental` module.
|
||||
// XXX: This check and its tests are structurally nearly identical to `IsInstanceLambdaUsage`.
|
||||
// Unless folded into `MethodReferenceUsage`, consider merging the two.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::cast` method reference over equivalent lambda expression",
|
||||
link = BUG_PATTERNS_BASE_URL + "ClassCastLambdaUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class ClassCastLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link ClassCastLambdaUsage} instance. */
|
||||
public ClassCastLambdaUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
if (tree.getParameters().size() != 1 || !(tree.getBody() instanceof TypeCastTree typeCast)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Type type = ASTHelpers.getType(typeCast);
|
||||
if (type == null || type.isParameterized() || type.isPrimitive()) {
|
||||
/*
|
||||
* The method reference syntax does not support casting to parameterized types. Additionally,
|
||||
* `Class#cast` does not support the same range of type conversions between (boxed) primitive
|
||||
* types as the cast operator.
|
||||
*/
|
||||
// XXX: Depending on the declared type of the value being cast, in some cases we _can_ rewrite
|
||||
// primitive casts. Add support for this.
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
VariableTree param = Iterables.getOnlyElement(tree.getParameters());
|
||||
if (!ASTHelpers.getSymbol(param).equals(ASTHelpers.getSymbol(typeCast.getExpression()))) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(
|
||||
tree, SourceCode.treeToString(typeCast.getType(), state) + ".class::cast"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher;
|
||||
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.VariableTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.Flags;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags static constants that do not follow the upper snake case naming
|
||||
* convention.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Constant variables should adhere to the `UPPER_SNAKE_CASE` naming convention",
|
||||
link = BUG_PATTERNS_BASE_URL + "ConstantNaming",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = STYLE)
|
||||
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */)
|
||||
public final class ConstantNaming extends BugChecker implements VariableTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<VariableTree> IS_CONSTANT =
|
||||
allOf(hasModifier(Modifier.STATIC), hasModifier(Modifier.FINAL));
|
||||
private static final Matcher<VariableTree> IS_PRIVATE = hasModifier(Modifier.PRIVATE);
|
||||
private static final Pattern SNAKE_CASE = Pattern.compile("([a-z])([A-Z])");
|
||||
private static final ImmutableSet<String> DEFAULT_EXEMPTED_NAMES =
|
||||
ImmutableSet.of("serialVersionUID");
|
||||
|
||||
/**
|
||||
* Flag using which constant names that must not be flagged (in addition to those defined by
|
||||
* {@link #DEFAULT_EXEMPTED_NAMES}) can be specified.
|
||||
*/
|
||||
private static final String ADDITIONAL_EXEMPTED_NAMES_FLAG =
|
||||
"CanonicalConstantNaming:ExemptedNames";
|
||||
|
||||
private final ImmutableSet<String> exemptedNames;
|
||||
|
||||
/** Instantiates a default {@link ConstantNaming} instance. */
|
||||
public ConstantNaming() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a customized {@link ConstantNaming}.
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
@Inject
|
||||
ConstantNaming(ErrorProneFlags flags) {
|
||||
exemptedNames =
|
||||
Sets.union(DEFAULT_EXEMPTED_NAMES, Flags.getSet(flags, ADDITIONAL_EXEMPTED_NAMES_FLAG))
|
||||
.immutableCopy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchVariable(VariableTree tree, VisitorState state) {
|
||||
String variableName = tree.getName().toString();
|
||||
if (!IS_CONSTANT.matches(tree, state) || exemptedNames.contains(variableName)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
String replacement = toUpperSnakeCase(variableName);
|
||||
if (replacement.equals(variableName)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
if (!IS_PRIVATE.matches(tree, state)) {
|
||||
description.setMessage(
|
||||
"%s; consider renaming to '%s', though note that this is not a private constant"
|
||||
.formatted(message(), replacement));
|
||||
} else if (isVariableNameInUse(replacement, state)) {
|
||||
description.setMessage(
|
||||
"%s; consider renaming to '%s', though note that a variable with this name is already declared"
|
||||
.formatted(message(), replacement));
|
||||
} else {
|
||||
description.addFix(SuggestedFixes.renameVariable(tree, replacement, state));
|
||||
}
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
private static String toUpperSnakeCase(String variableName) {
|
||||
return SNAKE_CASE.matcher(variableName).replaceAll("$1_$2").toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static boolean isVariableNameInUse(String name, VisitorState state) {
|
||||
return Boolean.TRUE.equals(
|
||||
new TreeScanner<Boolean, @Nullable Void>() {
|
||||
@Override
|
||||
public Boolean visitVariable(VariableTree tree, @Nullable Void unused) {
|
||||
return ASTHelpers.getSymbol(tree).getSimpleName().contentEquals(name)
|
||||
|| super.visitVariable(tree, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean reduce(Boolean r1, Boolean r2) {
|
||||
return Boolean.TRUE.equals(r1) || Boolean.TRUE.equals(r2);
|
||||
}
|
||||
}.scan(state.getPath().getCompilationUnit(), null));
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
|
||||
}
|
||||
|
||||
private static boolean isInPossibleTestHelperClass(VisitorState state) {
|
||||
return Optional.ofNullable(ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class))
|
||||
return Optional.ofNullable(state.findEnclosing(ClassTree.class))
|
||||
.map(ClassTree::getSimpleName)
|
||||
.filter(name -> name.toString().contains("Test"))
|
||||
.isPresent();
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.MoreASTHelpers;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
@@ -203,14 +204,10 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
|
||||
ExpressionTree argument = ASTHelpers.stripParentheses(arguments.get(argPosition));
|
||||
return argument instanceof BinaryTree
|
||||
&& isStringTyped(argument, state)
|
||||
&& MoreASTHelpers.isStringTyped(argument, state)
|
||||
&& ASTHelpers.constValue(argument, String.class) == null;
|
||||
}
|
||||
|
||||
private static boolean isStringTyped(ExpressionTree tree, VisitorState state) {
|
||||
return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state);
|
||||
}
|
||||
|
||||
private static class ReplacementArgumentsConstructor
|
||||
extends SimpleTreeVisitor<@Nullable Void, VisitorState> {
|
||||
private final StringBuilder formatString = new StringBuilder();
|
||||
@@ -223,7 +220,7 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitBinary(BinaryTree tree, VisitorState state) {
|
||||
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
|
||||
if (tree.getKind() == Kind.PLUS && MoreASTHelpers.isStringTyped(tree, state)) {
|
||||
tree.getLeftOperand().accept(this, state);
|
||||
tree.getRightOperand().accept(this, state);
|
||||
} else {
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Types;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
@@ -51,6 +52,7 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
// 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.
|
||||
// XXX: Also flag redundant round-trip conversions such as `path.toFile().toPath()`.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid or clarify identity conversions",
|
||||
@@ -83,6 +85,7 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
|
||||
ImmutableSetMultimap.class.getCanonicalName(),
|
||||
ImmutableTable.class.getCanonicalName())
|
||||
.named("copyOf"),
|
||||
staticMethod().onClass(Instant.class.getCanonicalName()).namedAnyOf("from"),
|
||||
staticMethod().onClass(Matchers.class.getCanonicalName()).namedAnyOf("allOf", "anyOf"),
|
||||
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
|
||||
staticMethod()
|
||||
|
||||
@@ -25,6 +25,8 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check of the
|
||||
// `error-prone-experimental` module.
|
||||
// XXX: This check and its tests are structurally nearly identical to `ClassCastLambdaUsage`. Unless
|
||||
// folded into `MethodReferenceUsage`, consider merging the two.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
|
||||
|
||||
@@ -16,7 +16,7 @@ import static tech.picnic.errorprone.utils.MoreJUnitMatchers.TEST_METHOD;
|
||||
import static tech.picnic.errorprone.utils.MoreMatchers.hasMetaAnnotation;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -70,7 +70,7 @@ public final class JUnitClassModifiers extends BugChecker implements ClassTreeMa
|
||||
SuggestedFixes.removeModifiers(
|
||||
tree.getModifiers(),
|
||||
state,
|
||||
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC))
|
||||
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC))
|
||||
.ifPresent(fixBuilder::merge);
|
||||
|
||||
if (!HAS_SPRING_CONFIGURATION_ANNOTATION.matches(tree, state)) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Comparators;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -34,10 +35,8 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Symtab;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -106,22 +105,19 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return sortArrayElements(tree, state)
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
Fix fix = sortArrayElements(tree, state);
|
||||
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
private Optional<Fix> sortArrayElements(AnnotationTree tree, VisitorState state) {
|
||||
private Fix sortArrayElements(AnnotationTree tree, VisitorState state) {
|
||||
/*
|
||||
* We loop over the array's attributes, trying to sort each array associated with a
|
||||
* non-blacklisted attribute. A single compound fix, if any, is returned.
|
||||
*/
|
||||
return matcher
|
||||
.extractMatchingArguments(tree)
|
||||
.map(expr -> extractArray(expr).flatMap(arr -> suggestSorting(arr, state)))
|
||||
.flatMap(Optional::stream)
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build);
|
||||
.flatMap(expr -> extractArray(expr).map(arr -> suggestSorting(arr, state)).stream())
|
||||
.collect(SuggestedFix.mergeFixes());
|
||||
}
|
||||
|
||||
private static Optional<NewArrayTree> extractArray(ExpressionTree expr) {
|
||||
@@ -130,18 +126,17 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
: Optional.of(expr).filter(NewArrayTree.class::isInstance).map(NewArrayTree.class::cast);
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> suggestSorting(
|
||||
NewArrayTree array, VisitorState state) {
|
||||
private static SuggestedFix suggestSorting(NewArrayTree array, VisitorState state) {
|
||||
if (array.getInitializers().size() < 2 || !canSort(array, state)) {
|
||||
/* There's nothing to sort, or we don't want to sort. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
List<? extends ExpressionTree> actualOrdering = array.getInitializers();
|
||||
ImmutableList<? extends ExpressionTree> desiredOrdering = doSort(actualOrdering);
|
||||
if (actualOrdering.equals(desiredOrdering)) {
|
||||
/* In the (presumably) common case the elements are already sorted. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
/* The elements aren't sorted. Suggest the sorted alternative. */
|
||||
@@ -149,7 +144,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
desiredOrdering.stream()
|
||||
.map(expr -> SourceCode.treeToString(expr, state))
|
||||
.collect(joining(", ", "{", "}"));
|
||||
return Optional.of(SuggestedFix.builder().replace(array, suggestion));
|
||||
return SuggestedFix.replace(array, suggestion);
|
||||
}
|
||||
|
||||
private static boolean canSort(Tree array, VisitorState state) {
|
||||
@@ -163,7 +158,12 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
|
||||
/* For now we don't force sorting on numeric types. */
|
||||
return Stream.of(
|
||||
symtab.annotationType, symtab.classType, symtab.enumSym.type, symtab.stringType)
|
||||
symtab.annotationType,
|
||||
symtab.booleanType,
|
||||
symtab.charType,
|
||||
symtab.classType,
|
||||
symtab.enumSym.type,
|
||||
symtab.stringType)
|
||||
.anyMatch(t -> ASTHelpers.isSubtype(elemType, t, state));
|
||||
}
|
||||
|
||||
@@ -225,10 +225,8 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
excludedAnnotations(flags));
|
||||
}
|
||||
|
||||
private static ImmutableList<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
Set<String> exclusions = new HashSet<>();
|
||||
exclusions.addAll(Flags.getList(flags, EXCLUDED_ANNOTATIONS_FLAG));
|
||||
exclusions.addAll(BLACKLISTED_ANNOTATIONS);
|
||||
return ImmutableList.copyOf(exclusions);
|
||||
private static ImmutableSet<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
return Sets.union(BLACKLISTED_ANNOTATIONS, Flags.getSet(flags, EXCLUDED_ANNOTATIONS_FLAG))
|
||||
.immutableCopy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import static java.util.Comparator.comparing;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -105,10 +104,7 @@ public final class LexicographicalAnnotationListing extends BugChecker
|
||||
originalAnnotations.stream(),
|
||||
sortedAnnotations.stream(),
|
||||
(original, replacement) ->
|
||||
SuggestedFix.builder()
|
||||
.replace(original, SourceCode.treeToString(replacement, state)))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.orElseThrow(() -> new VerifyException("No annotations were provided"));
|
||||
SuggestedFix.replace(original, SourceCode.treeToString(replacement, state)))
|
||||
.collect(SuggestedFix.mergeFixes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,12 @@ import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
@@ -36,12 +34,12 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
* it does, the suggested fix changes the program's semantics. Such fragile code must instead be
|
||||
* refactored such that the side-effectful code does not appear accidental.
|
||||
*/
|
||||
// XXX: Consider also implementing the inverse, in which `.orElseGet(() -> someConstant)` is
|
||||
// flagged.
|
||||
// XXX: Once the `MethodReferenceUsageCheck` becomes generally usable, consider leaving the method
|
||||
// reference cleanup to that check, and express the remainder of the logic in this class using a
|
||||
// Refaster template, i.c.w. a `@Matches` constraint that implements the `requiresComputation`
|
||||
// logic.
|
||||
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Review whether a `@Matcher` can be used to avoid this.
|
||||
// XXX: Once the `MethodReferenceUsageCheck` bug checker becomes generally usable, consider leaving
|
||||
// the method reference cleanup to that check, and express the remainder of the logic in this class
|
||||
// using a Refaster template, i.c.w. a `@NotMatches(RequiresComputation.class)` constraint.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
@@ -51,16 +49,17 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
linkType = NONE,
|
||||
severity = WARNING,
|
||||
tags = PERFORMANCE)
|
||||
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
public final class OptionalOrElseGet extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> REQUIRES_COMPUTATION = new RequiresComputation();
|
||||
private static final Matcher<ExpressionTree> OPTIONAL_OR_ELSE_METHOD =
|
||||
instanceMethod().onExactClass(Optional.class.getCanonicalName()).namedAnyOf("orElse");
|
||||
// XXX: Also exclude invocations of `@Placeholder`-annotated methods.
|
||||
private static final Matcher<ExpressionTree> REFASTER_METHOD =
|
||||
staticMethod().onClass(Refaster.class.getCanonicalName());
|
||||
|
||||
/** Instantiates a new {@link OptionalOrElse} instance. */
|
||||
public OptionalOrElse() {}
|
||||
/** Instantiates a new {@link OptionalOrElseGet} instance. */
|
||||
public OptionalOrElseGet() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
@@ -69,7 +68,8 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
}
|
||||
|
||||
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
|
||||
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
|
||||
if (!REQUIRES_COMPUTATION.matches(argument, state)
|
||||
|| REFASTER_METHOD.matches(argument, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -91,18 +91,6 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
return describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given expression contains anything other than a literal or a (possibly
|
||||
* dereferenced) variable or constant.
|
||||
*/
|
||||
private static boolean requiresComputation(ExpressionTree tree) {
|
||||
return !(tree instanceof IdentifierTree
|
||||
|| tree instanceof LiteralTree
|
||||
|| (tree instanceof MemberSelectTree memberSelect
|
||||
&& !requiresComputation(memberSelect.getExpression()))
|
||||
|| ASTHelpers.constValue(tree) != null);
|
||||
}
|
||||
|
||||
/** Returns the nullary method reference matching the given expression, if any. */
|
||||
private static Optional<String> tryMethodReferenceConversion(
|
||||
ExpressionTree tree, VisitorState state) {
|
||||
@@ -118,7 +106,7 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (requiresComputation(memberSelect.getExpression())) {
|
||||
if (REQUIRES_COMPUTATION.matches(memberSelect.getExpression(), state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
@@ -85,22 +86,22 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
}
|
||||
|
||||
return getPotentiallyBoxedReturnType(tree.getArguments().get(0))
|
||||
.flatMap(cmpType -> attemptMethodInvocationReplacement(tree, cmpType, isStatic, state))
|
||||
.map(cmpType -> attemptMethodInvocationReplacement(tree, cmpType, isStatic, state))
|
||||
.filter(not(SuggestedFix::isEmpty))
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> attemptMethodInvocationReplacement(
|
||||
private static SuggestedFix attemptMethodInvocationReplacement(
|
||||
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
|
||||
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
|
||||
String preferredMethodName = getPreferredMethod(cmpType, isStatic, state);
|
||||
if (actualMethodName.equals(preferredMethodName)) {
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
suggestFix(
|
||||
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state));
|
||||
return suggestFix(
|
||||
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +170,7 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static Fix suggestFix(
|
||||
private static SuggestedFix suggestFix(
|
||||
MethodInvocationTree tree, String preferredMethodName, VisitorState state) {
|
||||
ExpressionTree expr = tree.getMethodSelect();
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ import com.sun.source.tree.Tree.Kind;
|
||||
import java.io.Console;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formattable;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
@@ -177,18 +176,18 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return createDescription(tree, tryFix(rhs, state, STRING));
|
||||
}
|
||||
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
|
||||
// XXX: Avoid trying to simplify the RHS twice.
|
||||
ExpressionTree preferredRhs = trySimplify(rhs, state).orElse(rhs);
|
||||
if (STRING.matches(preferredRhs, state)) {
|
||||
tryFix(lhs, state, ANY_EXPR).ifPresent(fixes::add);
|
||||
fix.merge(tryFix(lhs, state, ANY_EXPR));
|
||||
} else {
|
||||
tryFix(lhs, state, STRING).ifPresent(fixes::add);
|
||||
fix.merge(tryFix(lhs, state, STRING));
|
||||
}
|
||||
tryFix(rhs, state, ANY_EXPR).ifPresent(fixes::add);
|
||||
fix.merge(tryFix(rhs, state, ANY_EXPR));
|
||||
|
||||
return createDescription(tree, fixes.stream().reduce(SuggestedFix.Builder::merge));
|
||||
return createDescription(tree, fix.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -229,17 +228,18 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return createDescription(tree, tryFix(tree, state, NON_NULL_STRING));
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> tryFixPositionalConverter(
|
||||
private SuggestedFix tryFixPositionalConverter(
|
||||
List<? extends ExpressionTree> arguments, VisitorState state, int index) {
|
||||
return Optional.of(arguments)
|
||||
.filter(args -> args.size() > index)
|
||||
.flatMap(args -> tryFix(args.get(index), state, ANY_EXPR));
|
||||
.map(args -> tryFix(args.get(index), state, ANY_EXPR))
|
||||
.orElseGet(SuggestedFix::emptyFix);
|
||||
}
|
||||
|
||||
// XXX: Write another check that checks that Formatter patterns don't use `{}` and have a
|
||||
// matching number of arguments of the appropriate type. Also flag explicit conversions from
|
||||
// `Formattable` to string.
|
||||
private Optional<SuggestedFix.Builder> tryFixFormatter(
|
||||
private SuggestedFix tryFixFormatter(
|
||||
List<? extends ExpressionTree> arguments, VisitorState state) {
|
||||
/*
|
||||
* Formatter methods have an optional first `Locale` parameter; if present, it must be
|
||||
@@ -258,7 +258,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return tryFixFormatterArguments(arguments, state, LOCALE, NOT_FORMATTABLE);
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> tryFixGuavaGuard(
|
||||
private SuggestedFix tryFixGuavaGuard(
|
||||
List<? extends ExpressionTree> arguments, VisitorState state) {
|
||||
/*
|
||||
* All Guava guard methods accept a value to be checked, a format string and zero or more
|
||||
@@ -271,7 +271,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
// number of arguments of the appropriate type. Also flag explicit conversions from `Throwable` to
|
||||
// string as the last logger argument. Suggests either dropping the conversion or going with
|
||||
// `Throwable#getMessage()` instead.
|
||||
private Optional<SuggestedFix.Builder> tryFixSlf4jLogger(
|
||||
private SuggestedFix tryFixSlf4jLogger(
|
||||
List<? extends ExpressionTree> arguments, VisitorState state) {
|
||||
/*
|
||||
* SLF4J treats the final argument to a log statement specially if it is a `Throwable`: it
|
||||
@@ -291,36 +291,34 @@ public final class RedundantStringConversion extends BugChecker
|
||||
omitLast ? arguments.subList(0, arguments.size() - 1) : arguments, state, MARKER, ANY_EXPR);
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> tryFixFormatterArguments(
|
||||
private SuggestedFix tryFixFormatterArguments(
|
||||
List<? extends ExpressionTree> arguments,
|
||||
VisitorState state,
|
||||
Matcher<ExpressionTree> firstArgFilter,
|
||||
Matcher<ExpressionTree> remainingArgFilter) {
|
||||
if (arguments.isEmpty()) {
|
||||
/* This format method accepts no arguments. Some odd overload? */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
int patternIndex = firstArgFilter.matches(arguments.get(0), state) ? 1 : 0;
|
||||
if (arguments.size() <= patternIndex) {
|
||||
/* This format method accepts only an ignored parameter. Some odd overload? */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
/* Simplify the values to be plugged into the format pattern, if possible. */
|
||||
return arguments.stream()
|
||||
.skip(patternIndex + 1L)
|
||||
.map(arg -> tryFix(arg, state, remainingArgFilter))
|
||||
.flatMap(Optional::stream)
|
||||
.reduce(SuggestedFix.Builder::merge);
|
||||
.collect(SuggestedFix.mergeFixes());
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> tryFix(
|
||||
private SuggestedFix tryFix(
|
||||
ExpressionTree tree, VisitorState state, Matcher<ExpressionTree> filter) {
|
||||
return trySimplify(tree, state, filter)
|
||||
.map(
|
||||
replacement ->
|
||||
SuggestedFix.builder().replace(tree, SourceCode.treeToString(replacement, state)));
|
||||
.map(replacement -> SuggestedFix.replace(tree, SourceCode.treeToString(replacement, state)))
|
||||
.orElseGet(SuggestedFix::emptyFix);
|
||||
}
|
||||
|
||||
private Optional<ExpressionTree> trySimplify(
|
||||
@@ -369,20 +367,17 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return Optional.of(Iterables.getOnlyElement(methodInvocation.getArguments()));
|
||||
}
|
||||
|
||||
private Description createDescription(Tree tree, Optional<SuggestedFix.Builder> fixes) {
|
||||
return fixes
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
private Description createDescription(Tree tree, SuggestedFix fix) {
|
||||
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
private static Matcher<MethodInvocationTree> createConversionMethodMatcher(
|
||||
ErrorProneFlags flags) {
|
||||
// XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas.
|
||||
// For this class methods accepting more than one argument are not valid, but still: not nice.
|
||||
// XXX: `Flags#getSet` splits by comma, but method signatures may also contain commas. For this
|
||||
// class methods accepting more than one argument are not valid, but still: not nice.
|
||||
return anyOf(
|
||||
WELL_KNOWN_STRING_CONVERSION_METHODS,
|
||||
new MethodMatcherFactory()
|
||||
.create(Flags.getList(flags, EXTRA_STRING_CONVERSION_METHODS_FLAG)));
|
||||
.create(Flags.getSet(flags, EXTRA_STRING_CONVERSION_METHODS_FLAG)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static tech.picnic.errorprone.utils.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.LiteralTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags string constants with extraneous escaping. */
|
||||
// XXX: Also cover `\"` sequences inside text blocks. Note that this requires a more subtle
|
||||
// approach, as double-quote characters will need to remain escaped if removing the backslash would
|
||||
// create a new sequence of three or more double-quotes. (TBD whether we'd like to enforce a
|
||||
// "preferred" approach to escaping, e.g. by always escaping the last of a triplet, such that the
|
||||
// over-all number of escaped characters is minimized.)
|
||||
// XXX: Also flag `'\"'` char literals.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Inside string expressions single quotes do not need to be escaped",
|
||||
link = BUG_PATTERNS_BASE_URL + "RedundantStringEscape",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class RedundantStringEscape extends BugChecker implements LiteralTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link RedundantStringEscape} instance. */
|
||||
public RedundantStringEscape() {}
|
||||
|
||||
@Override
|
||||
public Description matchLiteral(LiteralTree tree, VisitorState state) {
|
||||
String constant = ASTHelpers.constValue(tree, String.class);
|
||||
if (constant == null || constant.indexOf('\'') < 0) {
|
||||
/* Fast path: this isn't a string constant with a single quote. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
String source = SourceCode.treeToString(tree, state);
|
||||
if (!containsBannedEscapeSequence(source)) {
|
||||
/* Semi-fast path: this expression doesn't contain an escaped single quote. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/* Slow path: suggest dropping the escape characters. */
|
||||
return describeMatch(tree, SuggestedFix.replace(tree, dropRedundantEscapeSequences(source)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given string constant source expression contains an escaped single quote.
|
||||
*
|
||||
* @implNote As the input is a literal Java string expression, it will start and end with a double
|
||||
* quote; as such any found backslash will not be the string's final character.
|
||||
*/
|
||||
private static boolean containsBannedEscapeSequence(String source) {
|
||||
for (int p = source.indexOf('\\'); p != -1; p = source.indexOf('\\', p + 2)) {
|
||||
if (source.charAt(p + 1) == '\'') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplifies the given string constant source expression by dropping the backslash preceding an
|
||||
* escaped single quote.
|
||||
*
|
||||
* @implNote Note that this method does not delegate to {@link
|
||||
* SourceCode#toStringConstantExpression}, as that operation may replace other Unicode
|
||||
* characters with their associated escape sequence.
|
||||
* @implNote As the input is a literal Java string expression, it will start and end with a double
|
||||
* quote; as such any found backslash will not be the string's final character.
|
||||
*/
|
||||
private static String dropRedundantEscapeSequences(String source) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (int p = 0; p < source.length(); p++) {
|
||||
char c = source.charAt(p);
|
||||
if (c != '\\' || source.charAt(p + 1) != '\'') {
|
||||
result.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
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.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -74,10 +74,10 @@ public final class RequestParamType extends BugChecker implements VariableTreeMa
|
||||
return allOf(
|
||||
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
|
||||
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)),
|
||||
not(isSubtypeOfAny(Flags.getList(flags, SUPPORTED_CUSTOM_TYPES_FLAG))));
|
||||
not(isSubtypeOfAny(Flags.getSet(flags, SUPPORTED_CUSTOM_TYPES_FLAG))));
|
||||
}
|
||||
|
||||
private static Matcher<Tree> isSubtypeOfAny(ImmutableList<String> inclusions) {
|
||||
private static Matcher<Tree> isSubtypeOfAny(ImmutableSet<String> inclusions) {
|
||||
return anyOf(
|
||||
inclusions.stream()
|
||||
.map(inclusion -> isSubtypeOf(Suppliers.typeFromString(inclusion)))
|
||||
|
||||
@@ -41,7 +41,7 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
tags = LIKELY_ERROR)
|
||||
public final class Slf4jLogStatement extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
|
||||
private static final Matcher<ExpressionTree> SLF4J_MARKER = isSubtypeOf("org.slf4j.Marker");
|
||||
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
|
||||
private static final Matcher<ExpressionTree> SLF4J_LOGGER_INVOCATION =
|
||||
instanceMethod()
|
||||
@@ -71,7 +71,7 @@ public final class Slf4jLogStatement extends BugChecker implements MethodInvocat
|
||||
* SLF4J log statements may accept a "marker" as a first argument, before the format string.
|
||||
* We ignore such markers.
|
||||
*/
|
||||
int lTrim = MARKER.matches(args.get(0), state) ? 1 : 0;
|
||||
int lTrim = SLF4J_MARKER.matches(args.get(0), state) ? 1 : 0;
|
||||
/*
|
||||
* SLF4J treats the final argument to a log statement specially if it is a `Throwabe`: it
|
||||
* will always choose to render the associated stacktrace, even if the argument has a
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.classLiteral;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.toType;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher;
|
||||
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.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.ModifiersTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import java.util.EnumSet;
|
||||
import javax.inject.Inject;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import tech.picnic.errorprone.utils.MoreASTHelpers;
|
||||
|
||||
/** A {@link BugChecker} that flags non-canonical SLF4J logger declarations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "SLF4J logger declarations should follow established best-practices",
|
||||
link = BUG_PATTERNS_BASE_URL + "Slf4jLoggerDeclaration",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = STYLE)
|
||||
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */)
|
||||
public final class Slf4jLoggerDeclaration extends BugChecker implements VariableTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> IS_GET_LOGGER =
|
||||
staticMethod().onDescendantOf("org.slf4j.LoggerFactory").named("getLogger");
|
||||
private static final String CANONICAL_STATIC_LOGGER_NAME_FLAG =
|
||||
"Slf4jLoggerDeclaration:CanonicalStaticLoggerName";
|
||||
private static final String DEFAULT_CANONICAL_LOGGER_NAME = "LOG";
|
||||
private static final Matcher<ExpressionTree> IS_STATIC_ENCLOSING_CLASS_REFERENCE =
|
||||
classLiteral(Slf4jLoggerDeclaration::isEnclosingClassReference);
|
||||
private static final Matcher<ExpressionTree> IS_DYNAMIC_ENCLOSING_CLASS_REFERENCE =
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
instanceMethod().anyClass().named("getClass").withNoParameters(),
|
||||
Slf4jLoggerDeclaration::getClassReceiverIsEnclosingClassInstance));
|
||||
private static final ImmutableSet<Modifier> INSTANCE_DECLARATION_MODIFIERS =
|
||||
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.FINAL);
|
||||
private static final ImmutableSet<Modifier> STATIC_DECLARATION_MODIFIERS =
|
||||
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);
|
||||
|
||||
private final String canonicalStaticFieldName;
|
||||
private final String canonicalInstanceFieldName;
|
||||
|
||||
/** Instantiates a default {@link Slf4jLoggerDeclaration} instance. */
|
||||
public Slf4jLoggerDeclaration() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a customized {@link Slf4jLoggerDeclaration}.
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
@Inject
|
||||
Slf4jLoggerDeclaration(ErrorProneFlags flags) {
|
||||
canonicalStaticFieldName =
|
||||
flags.get(CANONICAL_STATIC_LOGGER_NAME_FLAG).orElse(DEFAULT_CANONICAL_LOGGER_NAME);
|
||||
canonicalInstanceFieldName =
|
||||
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, canonicalStaticFieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchVariable(VariableTree tree, VisitorState state) {
|
||||
ExpressionTree initializer = tree.getInitializer();
|
||||
if (!IS_GET_LOGGER.matches(initializer, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ClassTree clazz = getEnclosingClass(state);
|
||||
ExpressionTree factoryArg =
|
||||
Iterables.getOnlyElement(((MethodInvocationTree) initializer).getArguments());
|
||||
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
|
||||
if (clazz.getModifiers().getFlags().contains(Modifier.ABSTRACT)
|
||||
&& IS_DYNAMIC_ENCLOSING_CLASS_REFERENCE.matches(factoryArg, state)) {
|
||||
/*
|
||||
* While generally we prefer `Logger` declarations to be static and named after their
|
||||
* enclosing class, we allow one exception: loggers in abstract classes with a name derived
|
||||
* from `getClass()`.
|
||||
*/
|
||||
suggestModifiers(tree, INSTANCE_DECLARATION_MODIFIERS, fix, state);
|
||||
suggestRename(tree, canonicalInstanceFieldName, fix, state);
|
||||
} else {
|
||||
suggestModifiers(
|
||||
tree,
|
||||
clazz.getKind() == Kind.INTERFACE ? ImmutableSet.of() : STATIC_DECLARATION_MODIFIERS,
|
||||
fix,
|
||||
state);
|
||||
suggestRename(tree, canonicalStaticFieldName, fix, state);
|
||||
|
||||
if (!MoreASTHelpers.isStringTyped(factoryArg, state)
|
||||
&& !IS_STATIC_ENCLOSING_CLASS_REFERENCE.matches(factoryArg, state)) {
|
||||
/*
|
||||
* Loggers with a custom string name are generally "special", but those with a name derived
|
||||
* from a class other than the one that encloses it are likely in error.
|
||||
*/
|
||||
fix.merge(SuggestedFix.replace(factoryArg, clazz.getSimpleName() + ".class"));
|
||||
}
|
||||
}
|
||||
|
||||
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix.build());
|
||||
}
|
||||
|
||||
private static void suggestModifiers(
|
||||
VariableTree tree,
|
||||
ImmutableSet<Modifier> modifiers,
|
||||
SuggestedFix.Builder fixBuilder,
|
||||
VisitorState state) {
|
||||
ModifiersTree modifiersTree =
|
||||
requireNonNull(ASTHelpers.getModifiers(tree), "`VariableTree` must have modifiers");
|
||||
SuggestedFixes.addModifiers(tree, modifiersTree, state, modifiers).ifPresent(fixBuilder::merge);
|
||||
SuggestedFixes.removeModifiers(
|
||||
modifiersTree, state, Sets.difference(EnumSet.allOf(Modifier.class), modifiers))
|
||||
.ifPresent(fixBuilder::merge);
|
||||
}
|
||||
|
||||
private static void suggestRename(
|
||||
VariableTree variableTree, String name, SuggestedFix.Builder fixBuilder, VisitorState state) {
|
||||
if (!variableTree.getName().contentEquals(name)) {
|
||||
fixBuilder.merge(SuggestedFixes.renameVariable(variableTree, name, state));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEnclosingClassReference(ExpressionTree tree, VisitorState state) {
|
||||
return ASTHelpers.getSymbol(getEnclosingClass(state)).equals(ASTHelpers.getSymbol(tree));
|
||||
}
|
||||
|
||||
private static boolean getClassReceiverIsEnclosingClassInstance(
|
||||
MethodInvocationTree getClassInvocationTree, VisitorState state) {
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(getClassInvocationTree);
|
||||
if (receiver == null) {
|
||||
/*
|
||||
* Method invocations without an explicit receiver either involve static methods (possibly
|
||||
* statically imported), or instance methods invoked on the enclosing class. As the given
|
||||
* `getClassInvocationTree` is guaranteed to be a nullary `#getClass()` invocation, the latter
|
||||
* must be the case.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
Symbol symbol = ASTHelpers.getSymbol(receiver);
|
||||
return symbol != null
|
||||
&& symbol.asType().tsym.equals(ASTHelpers.getSymbol(getEnclosingClass(state)));
|
||||
}
|
||||
|
||||
private static ClassTree getEnclosingClass(VisitorState state) {
|
||||
ClassTree clazz = state.findEnclosing(ClassTree.class);
|
||||
// XXX: Review whether we should relax this constraint in the face of so-called anonymous
|
||||
// classes. See
|
||||
// https://docs.oracle.com/en/java/javase/23/language/implicitly-declared-classes-and-instance-main-methods.html
|
||||
verify(clazz != null, "Variable not defined inside class");
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.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;
|
||||
@@ -65,16 +64,18 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
return ARGUMENT_SELECTOR
|
||||
.extractMatchingArguments(tree)
|
||||
.findFirst()
|
||||
.flatMap(arg -> trySimplification(tree, arg, state))
|
||||
.map(arg -> trySimplification(tree, arg, state))
|
||||
.filter(not(SuggestedFix::isEmpty))
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> trySimplification(
|
||||
private static SuggestedFix trySimplification(
|
||||
AnnotationTree tree, ExpressionTree arg, VisitorState state) {
|
||||
return extractUniqueMethod(arg, state)
|
||||
.map(REPLACEMENTS::get)
|
||||
.map(newAnnotation -> replaceAnnotation(tree, arg, newAnnotation, state));
|
||||
.map(newAnnotation -> replaceAnnotation(tree, arg, newAnnotation, state))
|
||||
.orElseGet(SuggestedFix::emptyFix);
|
||||
}
|
||||
|
||||
private static Optional<String> extractUniqueMethod(ExpressionTree arg, VisitorState state) {
|
||||
@@ -99,7 +100,7 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
};
|
||||
}
|
||||
|
||||
private static Fix replaceAnnotation(
|
||||
private static SuggestedFix replaceAnnotation(
|
||||
AnnotationTree tree, ExpressionTree argToRemove, String newAnnotation, VisitorState state) {
|
||||
String newArguments =
|
||||
tree.getArguments().stream()
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 java.util.Objects.requireNonNull;
|
||||
import static java.util.function.Predicate.not;
|
||||
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.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
@@ -34,7 +35,6 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.StaticImports;
|
||||
import com.google.errorprone.bugpatterns.StaticImports.StaticImportInfo;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
@@ -203,7 +203,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
}
|
||||
|
||||
return getCandidateSimpleName(importInfo)
|
||||
.flatMap(n -> tryStaticImport(tree, importInfo.canonicalName() + '.' + n, n, state))
|
||||
.map(n -> tryStaticImport(tree, importInfo.canonicalName() + '.' + n, n, state))
|
||||
.filter(not(SuggestedFix::isEmpty))
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
@@ -240,15 +241,15 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
|| STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(canonicalName, name));
|
||||
}
|
||||
|
||||
private static Optional<Fix> tryStaticImport(
|
||||
private static SuggestedFix tryStaticImport(
|
||||
MemberSelectTree tree, String fullyQualifiedName, String simpleName, VisitorState state) {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder().replace(tree, simpleName);
|
||||
|
||||
if (!simpleName.equals(SuggestedFixes.qualifyStaticImport(fullyQualifiedName, fix, state))) {
|
||||
/* Statically importing this symbol would clash with an existing import. */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
return Optional.of(fix.build());
|
||||
return fix.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ 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.Constants;
|
||||
import java.util.Formattable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -150,7 +149,7 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(tree.getMethodSelect(), "String.join")
|
||||
.replace(arguments.next(), Constants.format(separator));
|
||||
.replace(arguments.next(), SourceCode.toStringConstantExpression(separator, state));
|
||||
|
||||
while (arguments.hasNext()) {
|
||||
ExpressionTree argument = arguments.next();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.toImmutableEnumSet;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Collections.disjoint;
|
||||
import static java.util.Objects.checkIndex;
|
||||
@@ -70,28 +68,6 @@ final class AssortedRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link
|
||||
* ImmutableSet#toImmutableSet()} and produces a more compact object.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving: while the
|
||||
* original code produces a set that iterates over the elements in encounter order, the
|
||||
* replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
// XXX: ^ Consider emitting a comment warning about this fact?
|
||||
static final class StreamToImmutableEnumSet<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Stream<T> stream) {
|
||||
return stream.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
ImmutableSet<T> after(Stream<T> stream) {
|
||||
return stream.collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Iterators#getNext(Iterator, Object)} over more contrived alternatives. */
|
||||
static final class IteratorGetNextOrDefault<T> {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -9,7 +9,9 @@ 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 javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/** Refaster rules related to {@link com.google.errorprone.bugpatterns.BugChecker} classes. */
|
||||
@OnlineDocumentation
|
||||
@@ -55,16 +57,44 @@ final class BugCheckerRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using the {@link Constants} API over more verbose alternatives. */
|
||||
/**
|
||||
* Prefer {@link SourceCode#toStringConstantExpression(Object,
|
||||
* com.google.errorprone.VisitorState)} over alternatives that unnecessarily escape single quote
|
||||
* characters.
|
||||
*/
|
||||
static final class ConstantsFormat {
|
||||
@BeforeTemplate
|
||||
String before(CharSequence value) {
|
||||
return Constants.format(value);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
String before(String value) {
|
||||
return String.format("\"%s\"", Convert.quote(value));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(String value) {
|
||||
return Constants.format(value);
|
||||
String after(CharSequence value) {
|
||||
return SourceCode.toStringConstantExpression(
|
||||
value, Refaster.emitCommentBefore("REPLACEME", null));
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Name#contentEquals(CharSequence)} over more verbose alternatives. */
|
||||
static final class NameContentEquals {
|
||||
@BeforeTemplate
|
||||
boolean before(Name name, CharSequence string) {
|
||||
return name.toString().equals(string.toString());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(Name name, String string) {
|
||||
return name.toString().equals(string);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(Name name, CharSequence string) {
|
||||
return name.contentEquals(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,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.BeforeTemplate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@@ -37,7 +38,12 @@ final class ClassRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
|
||||
/**
|
||||
* Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require
|
||||
* naming a variable.
|
||||
*/
|
||||
// XXX: Once the `ClassReferenceIsInstancePredicate` rule is dropped, rename this rule to just
|
||||
// `ClassIsInstancePredicate`.
|
||||
static final class ClassLiteralIsInstancePredicate<T, S> {
|
||||
@BeforeTemplate
|
||||
Predicate<S> before() {
|
||||
@@ -50,7 +56,11 @@ final class ClassRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
|
||||
/**
|
||||
* Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require
|
||||
* naming a variable.
|
||||
*/
|
||||
// XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default.
|
||||
static final class ClassReferenceIsInstancePredicate<T, S> {
|
||||
@BeforeTemplate
|
||||
Predicate<S> before(Class<T> clazz) {
|
||||
@@ -62,4 +72,21 @@ final class ClassRules {
|
||||
return clazz::isInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Class#cast(Object)} method references over lambda expressions that require naming
|
||||
* a variable.
|
||||
*/
|
||||
// XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default.
|
||||
static final class ClassReferenceCast<T, S> {
|
||||
@BeforeTemplate
|
||||
Function<T, S> before(Class<? extends S> clazz) {
|
||||
return o -> clazz.cast(o);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Function<T, S> after(Class<? extends S> clazz) {
|
||||
return clazz::cast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ 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 com.google.errorprone.refaster.annotation.NotMatches;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -21,6 +23,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsRefasterAsVarargs;
|
||||
|
||||
/** Refaster rules related to expressions dealing with (arbitrary) collections. */
|
||||
// XXX: There are other Guava `Iterables` methods that should not be called if the input is known to
|
||||
@@ -184,6 +187,24 @@ final class CollectionRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily call {@link Stream#distinct()} on an already-unique stream of elements. */
|
||||
// XXX: This rule assumes that the `Set` relies on `Object#equals`, rather than a custom
|
||||
// equivalence relation.
|
||||
// XXX: Expressions that drop or reorder elements from the stream, such as `.filter`, `.skip` and
|
||||
// `sorted`, can similarly be simplified. Covering all cases is better done using an Error Prone
|
||||
// check.
|
||||
static final class SetStream<T> {
|
||||
@BeforeTemplate
|
||||
Stream<?> before(Set<T> set) {
|
||||
return set.stream().distinct();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Stream<?> after(Set<T> set) {
|
||||
return set.stream();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link ArrayList#ArrayList(Collection)} over the Guava alternative. */
|
||||
@SuppressWarnings(
|
||||
"NonApiType" /* Matching against `List` would unnecessarily constrain the rule. */)
|
||||
@@ -276,6 +297,23 @@ final class CollectionRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Arrays#asList(Object[])} over more contrived alternatives. */
|
||||
// XXX: Consider moving this rule to `ImmutableListRules` and having it suggest
|
||||
// `ImmutableList#copyOf`. That would retain immutability, at the cost of no longer handling
|
||||
// `null`s.
|
||||
static final class ArraysAsList<T> {
|
||||
// XXX: This expression produces an unmodifiable list, while the alternative doesn't.
|
||||
@BeforeTemplate
|
||||
List<T> before(@NotMatches(IsRefasterAsVarargs.class) T[] array) {
|
||||
return Arrays.stream(array).toList();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
List<T> after(T[] array) {
|
||||
return Arrays.asList(array);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer calling {@link Collection#toArray()} over more contrived alternatives. */
|
||||
static final class CollectionToArray<T> {
|
||||
@BeforeTemplate
|
||||
@@ -327,18 +365,20 @@ final class CollectionRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call {@link ImmutableCollection#asList()} if {@link ImmutableCollection#iterator()} is
|
||||
* called on the result; call it directly.
|
||||
*/
|
||||
static final class ImmutableCollectionIterator<T> {
|
||||
/** Prefer {@link Collection#iterator()} over more contrived or less efficient alternatives. */
|
||||
static final class CollectionIterator<T> {
|
||||
@BeforeTemplate
|
||||
Iterator<T> before(Collection<T> collection) {
|
||||
return collection.stream().iterator();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Iterator<T> before(ImmutableCollection<T> collection) {
|
||||
return collection.asList().iterator();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterator<T> after(ImmutableCollection<T> collection) {
|
||||
Iterator<T> after(Collection<T> collection) {
|
||||
return collection.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
@@ -243,18 +245,77 @@ final class ComparatorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#sort(List)} over more verbose alternatives. */
|
||||
static final class CollectionsSort<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
void before(List<T> collection) {
|
||||
Collections.sort(collection, naturalOrder());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
void after(List<T> collection) {
|
||||
Collections.sort(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#min(Collection)} over more verbose alternatives. */
|
||||
static final class CollectionsMin<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection) {
|
||||
return Refaster.anyOf(
|
||||
Collections.min(collection, naturalOrder()), Collections.max(collection, reverseOrder()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection) {
|
||||
return Collections.min(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MinOfVarargs<T> {
|
||||
static final class MinOfArray<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<T> cmp) {
|
||||
T before(T[] array, Comparator<S> cmp) {
|
||||
return Arrays.stream(array).min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(T[] array, Comparator<S> cmp) {
|
||||
return Collections.min(Arrays.asList(array), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class CollectionsMinWithComparator<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection, Comparator<S> cmp) {
|
||||
return collection.stream().min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection, Comparator<S> cmp) {
|
||||
return Collections.min(collection, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MinOfVarargs<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<S> cmp) {
|
||||
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(@Repeated T value, Comparator<T> cmp) {
|
||||
T after(@Repeated T value, Comparator<S> cmp) {
|
||||
return Collections.min(Arrays.asList(value), cmp);
|
||||
}
|
||||
}
|
||||
@@ -310,18 +371,64 @@ final class ComparatorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#max(Collection)} over more verbose alternatives. */
|
||||
static final class CollectionsMax<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection) {
|
||||
return Refaster.anyOf(
|
||||
Collections.max(collection, naturalOrder()), Collections.min(collection, reverseOrder()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection) {
|
||||
return Collections.max(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MaxOfVarargs<T> {
|
||||
static final class MaxOfArray<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<T> cmp) {
|
||||
T before(T[] array, Comparator<S> cmp) {
|
||||
return Arrays.stream(array).max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(T[] array, Comparator<S> cmp) {
|
||||
return Collections.max(Arrays.asList(array), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class CollectionsMaxWithComparator<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection, Comparator<S> cmp) {
|
||||
return collection.stream().max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection, Comparator<S> cmp) {
|
||||
return Collections.max(collection, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MaxOfVarargs<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<S> cmp) {
|
||||
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(@Repeated T value, Comparator<T> cmp) {
|
||||
T after(@Repeated T value, Comparator<S> cmp) {
|
||||
return Collections.max(Arrays.asList(value), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,18 @@ package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with files. */
|
||||
@@ -15,6 +21,49 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
final class FileRules {
|
||||
private FileRules() {}
|
||||
|
||||
/** Prefer the more idiomatic {@link Path#of(URI)} over {@link Paths#get(URI)}. */
|
||||
static final class PathOfUri {
|
||||
@BeforeTemplate
|
||||
Path before(URI uri) {
|
||||
return Paths.get(uri);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(URI uri) {
|
||||
return Path.of(uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer the more idiomatic {@link Path#of(String, String...)} over {@link Paths#get(String,
|
||||
* String...)}.
|
||||
*/
|
||||
static final class PathOfString {
|
||||
@BeforeTemplate
|
||||
Path before(String first, @Repeated String more) {
|
||||
return Paths.get(first, more);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(String first, @Repeated String more) {
|
||||
return Path.of(first, more);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid redundant conversions from {@link Path} to {@link File}. */
|
||||
// XXX: Review whether a rule such as this one is better handled by the `IdentityConversion` rule.
|
||||
static final class PathInstance {
|
||||
@BeforeTemplate
|
||||
Path before(Path path) {
|
||||
return path.toFile().toPath();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(Path path) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Files#readString(Path, Charset)} over more contrived alternatives. */
|
||||
static final class FilesReadStringWithCharset {
|
||||
@BeforeTemplate
|
||||
@@ -40,4 +89,56 @@ final class FileRules {
|
||||
return Files.readString(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Files#createTempFile(String, String, FileAttribute[])} over alternatives that
|
||||
* create files with more liberal permissions.
|
||||
*
|
||||
* <p>Note that {@link File#createTempFile} treats the given prefix as a path, and ignores all but
|
||||
* its file name. That is, the actual prefix used is derived from all characters following the
|
||||
* final file separator (if any). This is not the case with {@link Files#createTempFile}, which
|
||||
* will instead throw an {@link IllegalArgumentException} if the prefix contains any file
|
||||
* separators.
|
||||
*/
|
||||
static final class FilesCreateTempFileToFile {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings({
|
||||
"FilesCreateTempFileInCustomDirectoryToFile" /* This is a more specific template. */,
|
||||
"java:S5443" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
File before(String prefix, String suffix) throws IOException {
|
||||
return Refaster.anyOf(
|
||||
File.createTempFile(prefix, suffix), File.createTempFile(prefix, suffix, null));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings(
|
||||
"java:S5443" /* On POSIX systems the file will only have user read-write permissions. */)
|
||||
File after(String prefix, String suffix) throws IOException {
|
||||
return Files.createTempFile(prefix, suffix).toFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Files#createTempFile(Path, String, String, FileAttribute[])} over alternatives
|
||||
* that create files with more liberal permissions.
|
||||
*
|
||||
* <p>Note that {@link File#createTempFile} treats the given prefix as a path, and ignores all but
|
||||
* its file name. That is, the actual prefix used is derived from all characters following the
|
||||
* final file separator (if any). This is not the case with {@link Files#createTempFile}, which
|
||||
* will instead throw an {@link IllegalArgumentException} if the prefix contains any file
|
||||
* separators.
|
||||
*/
|
||||
static final class FilesCreateTempFileInCustomDirectoryToFile {
|
||||
@BeforeTemplate
|
||||
File before(File directory, String prefix, String suffix) throws IOException {
|
||||
return File.createTempFile(prefix, suffix, directory);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
File after(File directory, String prefix, String suffix) throws IOException {
|
||||
return Files.createTempFile(directory.toPath(), prefix, suffix).toFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.toImmutableEnumSet;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
* Refaster rules related to expressions dealing with {@code
|
||||
* com.google.common.collect.ImmutableEnumSet}s.
|
||||
*/
|
||||
// XXX: Some of the rules defined here impact iteration order. That's a rather subtle change. Should
|
||||
// we emit a comment warning about this fact? (This may produce a lot of noise. A bug checker could
|
||||
// in some cases determine whether iteration order is important.)
|
||||
// XXX: Consider replacing the `SetsImmutableEnumSet[N]` Refaster rules with a bug checker, such
|
||||
// that call to `ImmutableSet#of(Object, Object, Object, Object, Object, Object, Object[])` with
|
||||
// enum-typed values can also be rewritten.
|
||||
@OnlineDocumentation
|
||||
final class ImmutableEnumSetRules {
|
||||
private ImmutableEnumSetRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Iterable)} for enum collections to take advantage of the
|
||||
* internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the
|
||||
* original code produces a set that iterates over its elements in the same order as the input
|
||||
* {@link Iterable}, the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSetIterable<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Iterable<T> elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Collection<T> elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> after(Iterable<T> elements) {
|
||||
return Sets.immutableEnumSet(elements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Iterable)} for enum collections to take advantage of the
|
||||
* internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the
|
||||
* original code produces a set that iterates over its elements in the same order as defined in
|
||||
* the array, the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSetArraysAsList<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(T[] elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> after(T[] elements) {
|
||||
return Sets.immutableEnumSet(Arrays.asList(elements));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet1<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1) {
|
||||
return Refaster.anyOf(ImmutableSet.of(e1), ImmutableSet.copyOf(EnumSet.of(e1)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1) {
|
||||
return Sets.immutableEnumSet(e1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the {@link
|
||||
* ImmutableSet#of} expression produces a set that iterates over its elements in the listed order,
|
||||
* the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet2<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, T e2) {
|
||||
return Refaster.anyOf(ImmutableSet.of(e1, e2), ImmutableSet.copyOf(EnumSet.of(e1, e2)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1, T e2) {
|
||||
return Sets.immutableEnumSet(e1, e2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the {@link
|
||||
* ImmutableSet#of} expression produces a set that iterates over its elements in the listed order,
|
||||
* the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet3<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, T e2, T e3) {
|
||||
return Refaster.anyOf(
|
||||
ImmutableSet.of(e1, e2, e3), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1, T e2, T e3) {
|
||||
return Sets.immutableEnumSet(e1, e2, e3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the {@link
|
||||
* ImmutableSet#of} expression produces a set that iterates over its elements in the listed order,
|
||||
* the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet4<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, T e2, T e3, T e4) {
|
||||
return Refaster.anyOf(
|
||||
ImmutableSet.of(e1, e2, e3, e4), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3, e4)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1, T e2, T e3, T e4) {
|
||||
return Sets.immutableEnumSet(e1, e2, e3, e4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the {@link
|
||||
* ImmutableSet#of} expression produces a set that iterates over its elements in the listed order,
|
||||
* the replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet5<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, T e2, T e3, T e4, T e5) {
|
||||
return Refaster.anyOf(
|
||||
ImmutableSet.of(e1, e2, e3, e4, e5), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3, e4, e5)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1, T e2, T e3, T e4, T e5) {
|
||||
return Sets.immutableEnumSet(e1, e2, e3, e4, e5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the
|
||||
* original code produces a set that iterates over its elements in the listed order, the
|
||||
* replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class SetsImmutableEnumSet6<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(T e1, T e2, T e3, T e4, T e5, T e6) {
|
||||
return ImmutableSet.of(e1, e2, e3, e4, e5, e6);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> after(T e1, T e2, T e3, T e4, T e5, T e6) {
|
||||
return Sets.immutableEnumSet(e1, e2, e3, e4, e5, e6);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of
|
||||
* the internally used {@link EnumSet}.
|
||||
*/
|
||||
static final class SetsImmutableEnumSetVarArgs<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, @Repeated T elements) {
|
||||
return ImmutableSet.copyOf(EnumSet.of(e1, Refaster.asVarargs(elements)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> after(T e1, @Repeated T elements) {
|
||||
return Sets.immutableEnumSet(e1, Refaster.asVarargs(elements));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link
|
||||
* ImmutableSet#toImmutableSet()} and produces a more compact object.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rule is not completely behavior preserving: while the
|
||||
* original code produces a set that iterates over its elements in encounter order, the
|
||||
* replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
static final class StreamToImmutableEnumSet<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Stream<T> stream) {
|
||||
return stream.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
ImmutableSet<T> after(Stream<T> stream) {
|
||||
return stream.collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with Micrometer. */
|
||||
// XXX: Consider replacing the `TagsOf[N]` rules with a bug checker, so that various other
|
||||
// expressions (e.g. those creating other collection types, those passing in tags some other way, or
|
||||
// those passing in more tags) can be replaced as wel.
|
||||
@OnlineDocumentation
|
||||
final class MicrometerRules {
|
||||
private MicrometerRules() {}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf1 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag), ImmutableList.of(tag));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag) {
|
||||
return Tags.of(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf2 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag1, tag2), ImmutableList.of(tag1, tag2));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag1, Tag tag2) {
|
||||
return Tags.of(tag1, tag2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf3 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2, Tag tag3) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag1, tag2, tag3), ImmutableList.of(tag1, tag2, tag3));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag1, Tag tag2, Tag tag3) {
|
||||
return Tags.of(tag1, tag2, tag3);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf4 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2, Tag tag3, Tag tag4) {
|
||||
return Refaster.anyOf(
|
||||
ImmutableSet.of(tag1, tag2, tag3, tag4), ImmutableList.of(tag1, tag2, tag3, tag4));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag1, Tag tag2, Tag tag3, Tag tag4) {
|
||||
return Tags.of(tag1, tag2, tag3, tag4);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf5 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2, Tag tag3, Tag tag4, Tag tag5) {
|
||||
return Refaster.anyOf(
|
||||
ImmutableSet.of(tag1, tag2, tag3, tag4, tag5),
|
||||
ImmutableList.of(tag1, tag2, tag3, tag4, tag5));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag1, Tag tag2, Tag tag3, Tag tag4, Tag tag5) {
|
||||
return Tags.of(tag1, tag2, tag3, tag4, tag5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;
|
||||
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link Optional}s. */
|
||||
@OnlineDocumentation
|
||||
@@ -255,24 +255,21 @@ final class OptionalRules {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the
|
||||
* fallback value is not the result of a trivial computation.
|
||||
* Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the
|
||||
* fallback value does not require non-trivial computation.
|
||||
*/
|
||||
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Review whether a `@Matcher` can be used to avoid this.
|
||||
// XXX: Once `MethodReferenceUsage` is "production ready", replace
|
||||
// `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and
|
||||
// reimplement the matcher accordingly).
|
||||
static final class OptionalOrElseGet<T> {
|
||||
// XXX: This rule is the counterpart to the `OptionalOrElseGet` bug checker. Once the
|
||||
// `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be
|
||||
// replaced with a Refaster rule.
|
||||
static final class OptionalOrElse<T> {
|
||||
@BeforeTemplate
|
||||
T before(Optional<T> optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
|
||||
return optional.orElse(value);
|
||||
T before(Optional<T> optional, @NotMatches(RequiresComputation.class) T value) {
|
||||
return optional.orElseGet(() -> value);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Optional<T> optional, T value) {
|
||||
return optional.orElseGet(() -> value);
|
||||
return optional.orElse(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +279,9 @@ final class OptionalRules {
|
||||
*/
|
||||
// XXX: Do we need the `.filter(Optional::isPresent)`? If it's absent the caller probably assumed
|
||||
// that the values are present. (If we drop it, we should rewrite vacuous filter steps.)
|
||||
// XXX: The rewritten `filter`/`map` expression may be more performant than its replacement. See
|
||||
// https://github.com/palantir/gradle-baseline/pull/2946. (There are plans to pair Refaster rules
|
||||
// with JMH benchmarks; this would be a great use case.)
|
||||
static final class StreamFlatMapOptional<T> {
|
||||
@BeforeTemplate
|
||||
Stream<T> before(Stream<Optional<T>> stream) {
|
||||
@@ -373,7 +373,12 @@ final class OptionalRules {
|
||||
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
|
||||
static final class OptionalOrOtherOptional<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
@SuppressWarnings({
|
||||
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
|
||||
"NestedOptionals" /* This violation will be rewritten. */,
|
||||
"OptionalOrElse" /* Parameters represent expressions that may require computation. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
|
||||
// XXX: Note that rewriting the first and third variant will change the code's behavior if
|
||||
// `optional2` has side-effects.
|
||||
@@ -393,10 +398,7 @@ final class OptionalRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary operations on an {@link Optional} that ultimately result in that very same
|
||||
* {@link Optional}.
|
||||
*/
|
||||
/** Don't unnecessarily transform an {@link Optional} to an equivalent instance. */
|
||||
static final class OptionalIdentity<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
|
||||
@@ -8,6 +8,8 @@ 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.common.primitives.UnsignedInts;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.AlsoNegation;
|
||||
@@ -76,6 +78,8 @@ final class PrimitiveRules {
|
||||
}
|
||||
|
||||
/** Prefer {@link Math#toIntExact(long)} over the Guava alternative. */
|
||||
// XXX: This rule changes the exception possibly thrown from `IllegalArgumentException` to
|
||||
// `ArithmeticException`.
|
||||
static final class LongToIntExact {
|
||||
@BeforeTemplate
|
||||
int before(long l) {
|
||||
@@ -192,97 +196,6 @@ final class PrimitiveRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Boolean#compare(boolean, boolean)} over the Guava alternative. */
|
||||
static final class BooleanCompare {
|
||||
@BeforeTemplate
|
||||
int before(boolean a, boolean b) {
|
||||
return Booleans.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(boolean a, boolean b) {
|
||||
return Boolean.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Character#compare(char, char)} over the Guava alternative. */
|
||||
static final class CharacterCompare {
|
||||
@BeforeTemplate
|
||||
int before(char a, char b) {
|
||||
return Chars.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(char a, char b) {
|
||||
return Character.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Short#compare(short, short)} over the Guava alternative. */
|
||||
static final class ShortCompare {
|
||||
@BeforeTemplate
|
||||
int before(short a, short b) {
|
||||
return Shorts.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(short a, short b) {
|
||||
return Short.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Integer#compare(int, int)} over the Guava alternative. */
|
||||
static final class IntegerCompare {
|
||||
@BeforeTemplate
|
||||
int before(int a, int b) {
|
||||
return Ints.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(int a, int b) {
|
||||
return Integer.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Long#compare(long, long)} over the Guava alternative. */
|
||||
static final class LongCompare {
|
||||
@BeforeTemplate
|
||||
int before(long a, long b) {
|
||||
return Longs.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(long a, long b) {
|
||||
return Long.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Float#compare(float, float)} over the Guava alternative. */
|
||||
static final class FloatCompare {
|
||||
@BeforeTemplate
|
||||
int before(float a, float b) {
|
||||
return Floats.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(float a, float b) {
|
||||
return Float.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Double#compare(double, double)} over the Guava alternative. */
|
||||
static final class DoubleCompare {
|
||||
@BeforeTemplate
|
||||
int before(double a, double b) {
|
||||
return Doubles.compare(a, b);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(double a, double b) {
|
||||
return Double.compare(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Character#BYTES} over the Guava alternative. */
|
||||
static final class CharacterBytes {
|
||||
@BeforeTemplate
|
||||
@@ -442,4 +355,205 @@ final class PrimitiveRules {
|
||||
return Long.signum(l) == -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Integer#compareUnsigned(int, int)} over third-party alternatives. */
|
||||
static final class IntegerCompareUnsigned {
|
||||
@BeforeTemplate
|
||||
int before(int x, int y) {
|
||||
return UnsignedInts.compare(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(int x, int y) {
|
||||
return Integer.compareUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Long#compareUnsigned(long, long)} over third-party alternatives. */
|
||||
static final class LongCompareUnsigned {
|
||||
@BeforeTemplate
|
||||
long before(long x, long y) {
|
||||
return UnsignedLongs.compare(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
long after(long x, long y) {
|
||||
return Long.compareUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Integer#divideUnsigned(int, int)} over third-party alternatives. */
|
||||
static final class IntegerDivideUnsigned {
|
||||
@BeforeTemplate
|
||||
int before(int x, int y) {
|
||||
return UnsignedInts.divide(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(int x, int y) {
|
||||
return Integer.divideUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Long#divideUnsigned(long, long)} over third-party alternatives. */
|
||||
static final class LongDivideUnsigned {
|
||||
@BeforeTemplate
|
||||
long before(long x, long y) {
|
||||
return UnsignedLongs.divide(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
long after(long x, long y) {
|
||||
return Long.divideUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Integer#remainderUnsigned(int, int)} over third-party alternatives. */
|
||||
static final class IntegerRemainderUnsigned {
|
||||
@BeforeTemplate
|
||||
int before(int x, int y) {
|
||||
return UnsignedInts.remainder(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(int x, int y) {
|
||||
return Integer.remainderUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Long#remainderUnsigned(long, long)} over third-party alternatives. */
|
||||
static final class LongRemainderUnsigned {
|
||||
@BeforeTemplate
|
||||
long before(long x, long y) {
|
||||
return UnsignedLongs.remainder(x, y);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
long after(long x, long y) {
|
||||
return Long.remainderUnsigned(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Integer#parseUnsignedInt(String)} over third-party or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class IntegerParseUnsignedInt {
|
||||
@BeforeTemplate
|
||||
int before(String string) {
|
||||
return Refaster.anyOf(
|
||||
UnsignedInts.parseUnsignedInt(string), Integer.parseUnsignedInt(string, 10));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string) {
|
||||
return Integer.parseUnsignedInt(string);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Long#parseUnsignedLong(String)} over third-party or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class LongParseUnsignedLong {
|
||||
@BeforeTemplate
|
||||
long before(String string) {
|
||||
return Refaster.anyOf(
|
||||
UnsignedLongs.parseUnsignedLong(string), Long.parseUnsignedLong(string, 10));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
long after(String string) {
|
||||
return Long.parseUnsignedLong(string);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Integer#parseUnsignedInt(String, int)} over third-party alternatives. */
|
||||
static final class IntegerParseUnsignedIntWithRadix {
|
||||
@BeforeTemplate
|
||||
int before(String string, int radix) {
|
||||
return UnsignedInts.parseUnsignedInt(string, radix);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, int radix) {
|
||||
return Integer.parseUnsignedInt(string, radix);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer JDK's {@link Long#parseUnsignedLong(String, int)} over third-party alternatives. */
|
||||
static final class LongParseUnsignedLongWithRadix {
|
||||
@BeforeTemplate
|
||||
long before(String string, int radix) {
|
||||
return UnsignedLongs.parseUnsignedLong(string, radix);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
long after(String string, int radix) {
|
||||
return Long.parseUnsignedLong(string, radix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Integer#toUnsignedString(int)} over third-party or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class IntegerToUnsignedString {
|
||||
@BeforeTemplate
|
||||
String before(int i) {
|
||||
return Refaster.anyOf(UnsignedInts.toString(i), Integer.toUnsignedString(i, 10));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(int i) {
|
||||
return Integer.toUnsignedString(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Long#toUnsignedString(long)} over third-party or more verbose alternatives.
|
||||
*/
|
||||
static final class LongToUnsignedString {
|
||||
@BeforeTemplate
|
||||
String before(long i) {
|
||||
return Refaster.anyOf(UnsignedLongs.toString(i), Long.toUnsignedString(i, 10));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(long i) {
|
||||
return Long.toUnsignedString(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Integer#toUnsignedString(int,int)} over third-party or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class IntegerToUnsignedStringWithRadix {
|
||||
@BeforeTemplate
|
||||
String before(int i, int radix) {
|
||||
return UnsignedInts.toString(i, radix);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(int i, int radix) {
|
||||
return Integer.toUnsignedString(i, radix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer JDK's {@link Long#toUnsignedString(long,int)} over third-party or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class LongToUnsignedStringWithRadix {
|
||||
@BeforeTemplate
|
||||
String before(long i, int radix) {
|
||||
return UnsignedLongs.toString(i, radix);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
String after(long i, int radix) {
|
||||
return Long.toUnsignedString(i, radix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -41,6 +42,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -1204,10 +1206,17 @@ final class ReactorRules {
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#fromIterable(Iterable)} over less efficient alternatives. */
|
||||
// XXX: Once the `FluxFromStreamSupplier` rule is constrained using
|
||||
// `@NotMatches(IsIdentityOperation.class)`, this rule should also cover
|
||||
// `Flux.fromStream(collection.stream())`.
|
||||
static final class FluxFromIterable<T> {
|
||||
// XXX: Once the `MethodReferenceUsage` check is generally enabled, drop the second
|
||||
// `Refaster.anyOf` variant.
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Collection<T> collection) {
|
||||
return Flux.fromStream(collection.stream());
|
||||
return Flux.fromStream(
|
||||
Refaster.<Supplier<Stream<? extends T>>>anyOf(
|
||||
collection::stream, () -> collection.stream()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -1759,6 +1768,60 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link StepVerifier#verify()} over a dangling {@link
|
||||
* StepVerifier#verifyThenAssertThat()}.
|
||||
*/
|
||||
// XXX: Application of this rule (and several others in this class) will cause invalid code if the
|
||||
// result of the rewritten expression is dereferenced. Consider introducing a bug checker that
|
||||
// identifies rules that change the return type of an expression and annotates them accordingly.
|
||||
// The associated annotation can then be used to instruct an annotation processor to generate
|
||||
// corresponding `void` rules that match only statements. This would allow the `Refaster` check to
|
||||
// conditionally skip "not fully safe" rules. This allows conditionally flagging more dubious
|
||||
// code, at the risk of compilation failures. With this rule, for example, we want to explicitly
|
||||
// nudge users towards `StepVerifier.Step#assertNext(Consumer)` or
|
||||
// `StepVerifier.Step#expectNext(Object)`, together with `Step#verifyComplete()`.
|
||||
static final class StepVerifierVerify {
|
||||
@BeforeTemplate
|
||||
StepVerifier.Assertions before(StepVerifier stepVerifier) {
|
||||
return stepVerifier.verifyThenAssertThat();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Duration after(StepVerifier stepVerifier) {
|
||||
return stepVerifier.verify();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link StepVerifier#verify(Duration)} over a dangling {@link
|
||||
* StepVerifier#verifyThenAssertThat(Duration)}.
|
||||
*/
|
||||
static final class StepVerifierVerifyDuration {
|
||||
@BeforeTemplate
|
||||
StepVerifier.Assertions before(StepVerifier stepVerifier, Duration duration) {
|
||||
return stepVerifier.verifyThenAssertThat(duration);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Duration after(StepVerifier stepVerifier, Duration duration) {
|
||||
return stepVerifier.verify(duration);
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily invoke {@link StepVerifier#verifyLater()} multiple times. */
|
||||
static final class StepVerifierVerifyLater {
|
||||
@BeforeTemplate
|
||||
StepVerifier before(StepVerifier stepVerifier) {
|
||||
return stepVerifier.verifyLater().verifyLater();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
StepVerifier after(StepVerifier stepVerifier) {
|
||||
return stepVerifier.verifyLater();
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */
|
||||
static final class StepVerifierStepIdentity<T> {
|
||||
@BeforeTemplate
|
||||
@@ -1859,6 +1922,12 @@ final class ReactorRules {
|
||||
return step.expectErrorMatches(predicate).verify();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("StepVerifierVerify" /* This is a more specific template. */)
|
||||
StepVerifier.Assertions before2(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
|
||||
return step.expectError().verifyThenAssertThat().hasOperatorErrorMatching(predicate);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Duration after(StepVerifier.LastStep step, Predicate<Throwable> predicate) {
|
||||
return step.verifyErrorMatches(predicate);
|
||||
@@ -1881,6 +1950,30 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link StepVerifier.LastStep#verifyErrorSatisfies(Consumer)} with AssertJ over more
|
||||
* contrived alternatives.
|
||||
*/
|
||||
static final class StepVerifierLastStepVerifyErrorSatisfiesAssertJ<T extends Throwable> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("StepVerifierVerify" /* This is a more specific template. */)
|
||||
StepVerifier.Assertions before(StepVerifier.LastStep step, Class<T> clazz, String message) {
|
||||
return Refaster.anyOf(
|
||||
step.expectError()
|
||||
.verifyThenAssertThat()
|
||||
.hasOperatorErrorOfType(clazz)
|
||||
.hasOperatorErrorWithMessage(message),
|
||||
step.expectError(clazz).verifyThenAssertThat().hasOperatorErrorWithMessage(message),
|
||||
step.expectErrorMessage(message).verifyThenAssertThat().hasOperatorErrorOfType(clazz));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
Duration after(StepVerifier.LastStep step, Class<T> clazz, String message) {
|
||||
return step.verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(clazz).hasMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link StepVerifier.LastStep#verifyErrorMessage(String)} over more verbose alternatives.
|
||||
*/
|
||||
@@ -1913,7 +2006,7 @@ final class ReactorRules {
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#fromFuture(Supplier)} over {@link Mono#fromFuture(CompletableFuture)}, as
|
||||
* the former may defer initiation of the asynchornous computation until subscription.
|
||||
* the former may defer initiation of the asynchronous computation until subscription.
|
||||
*/
|
||||
static final class MonoFromFutureSupplier<T> {
|
||||
// XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once
|
||||
@@ -1932,7 +2025,7 @@ final class ReactorRules {
|
||||
/**
|
||||
* Prefer {@link Mono#fromFuture(Supplier, boolean)} over {@link
|
||||
* Mono#fromFuture(CompletableFuture, boolean)}, as the former may defer initiation of the
|
||||
* asynchornous computation until subscription.
|
||||
* asynchronous computation until subscription.
|
||||
*/
|
||||
static final class MonoFromFutureSupplierBoolean<T> {
|
||||
// XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once
|
||||
@@ -1947,4 +2040,39 @@ final class ReactorRules {
|
||||
return Mono.fromFuture(() -> future, suppressCancel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't propagate {@link Mono} cancellations to an upstream cache value computation, as
|
||||
* completion of such computations may benefit concurrent or subsequent cache usages.
|
||||
*/
|
||||
static final class MonoFromFutureAsyncLoadingCacheGet<K, V> {
|
||||
@BeforeTemplate
|
||||
Mono<V> before(AsyncLoadingCache<K, V> cache, K key) {
|
||||
return Mono.fromFuture(() -> cache.get(key));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<V> after(AsyncLoadingCache<K, V> cache, K key) {
|
||||
return Mono.fromFuture(() -> cache.get(key), /* suppressCancel= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former
|
||||
* yields a {@link Flux} that is more likely to behave as expected when subscribed to more than
|
||||
* once.
|
||||
*/
|
||||
static final class FluxFromStreamSupplier<T> {
|
||||
// XXX: Constrain the `stream` parameter using `@NotMatches(IsIdentityOperation.class)` once
|
||||
// `IsIdentityOperation` no longer matches nullary method invocations.
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Stream<T> stream) {
|
||||
return Flux.fromStream(stream);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Stream<T> stream) {
|
||||
return Flux.fromStream(() -> stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,4 +244,105 @@ final class StringRules {
|
||||
return Utf8.encodedLength(str);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#indexOf(int, int)} over less efficient alternatives. */
|
||||
static final class StringIndexOfChar {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4635" /* This violation will be rewritten. */)
|
||||
int before(String string, int ch, int fromIndex) {
|
||||
return string.substring(fromIndex).indexOf(ch);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, int ch, int fromIndex) {
|
||||
return Math.max(-1, string.indexOf(ch, fromIndex) - fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#indexOf(String, int)} over less efficient alternatives. */
|
||||
static final class StringIndexOfString {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4635" /* This violation will be rewritten. */)
|
||||
int before(String string, String substring, int fromIndex) {
|
||||
return string.substring(fromIndex).indexOf(substring);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, String substring, int fromIndex) {
|
||||
return Math.max(-1, string.indexOf(substring, fromIndex) - fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Once we compile Refaster templates with JDK 21 also suggest `String#indexOf(int, int,
|
||||
// int)` and `String#indexOf(String, int, int)`.
|
||||
|
||||
/** Prefer {@link String#lastIndexOf(int, int)} over less efficient alternatives. */
|
||||
static final class StringLastIndexOfChar {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4635" /* This violation will be rewritten. */)
|
||||
int before(String string, int ch, int fromIndex) {
|
||||
return string.substring(fromIndex).lastIndexOf(ch);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, int ch, int fromIndex) {
|
||||
return Math.max(-1, string.lastIndexOf(ch) - fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#lastIndexOf(String, int)} over less efficient alternatives. */
|
||||
static final class StringLastIndexOfString {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4635" /* This violation will be rewritten. */)
|
||||
int before(String string, String substring, int fromIndex) {
|
||||
return string.substring(fromIndex).lastIndexOf(substring);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, String substring, int fromIndex) {
|
||||
return Math.max(-1, string.lastIndexOf(substring) - fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#lastIndexOf(int, int)} over less efficient alternatives. */
|
||||
static final class StringLastIndexOfCharWithIndex {
|
||||
@BeforeTemplate
|
||||
int before(String string, int ch, int fromIndex) {
|
||||
return string.substring(0, fromIndex).lastIndexOf(ch);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, int ch, int fromIndex) {
|
||||
return string.lastIndexOf(ch, fromIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#lastIndexOf(String, int)} over less efficient alternatives. */
|
||||
// XXX: The replacement expression isn't fully equivalent: in case `substring` is empty, then
|
||||
// the replacement yields `fromIndex - 1` rather than `fromIndex`.
|
||||
static final class StringLastIndexOfStringWithIndex {
|
||||
@BeforeTemplate
|
||||
int before(String string, String substring, int fromIndex) {
|
||||
return string.substring(0, fromIndex).lastIndexOf(substring);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
int after(String string, String substring, int fromIndex) {
|
||||
return string.lastIndexOf(substring, fromIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link String#startsWith(String, int)} over less efficient alternatives. */
|
||||
static final class StringStartsWith {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S4635" /* This violation will be rewritten. */)
|
||||
boolean before(String string, String prefix, int fromIndex) {
|
||||
return string.substring(fromIndex).startsWith(prefix);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(String string, String prefix, int fromIndex) {
|
||||
return string.startsWith(prefix, fromIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,63 @@ final class TimeRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily transform an {@link Instant} to an equivalent instance. */
|
||||
static final class InstantIdentity {
|
||||
@BeforeTemplate
|
||||
Instant before(Instant instant, TemporalUnit temporalUnit) {
|
||||
return Refaster.anyOf(
|
||||
instant.plus(Duration.ZERO),
|
||||
instant.plus(0, temporalUnit),
|
||||
instant.plusNanos(0),
|
||||
instant.plusMillis(0),
|
||||
instant.plusSeconds(0),
|
||||
instant.minus(Duration.ZERO),
|
||||
instant.minus(0, temporalUnit),
|
||||
instant.minusNanos(0),
|
||||
instant.minusMillis(0),
|
||||
instant.minusSeconds(0),
|
||||
Instant.parse(instant.toString()),
|
||||
instant.truncatedTo(ChronoUnit.NANOS),
|
||||
Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Instant after(Instant instant) {
|
||||
return instant;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Instant#truncatedTo(TemporalUnit)} over less obvious alternatives.
|
||||
*
|
||||
* <p>Note that {@link Instant#toEpochMilli()} throws an {@link ArithmeticException} for dates
|
||||
* very far in the past or future, while the suggested alternative doesn't.
|
||||
*/
|
||||
static final class InstantTruncatedToMilliseconds {
|
||||
@BeforeTemplate
|
||||
Instant before(Instant instant) {
|
||||
return Instant.ofEpochMilli(instant.toEpochMilli());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Instant after(Instant instant) {
|
||||
return instant.truncatedTo(ChronoUnit.MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#truncatedTo(TemporalUnit)} over less obvious alternatives. */
|
||||
static final class InstantTruncatedToSeconds {
|
||||
@BeforeTemplate
|
||||
Instant before(Instant instant) {
|
||||
return Instant.ofEpochSecond(instant.getEpochSecond());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Instant after(Instant instant) {
|
||||
return instant.truncatedTo(ChronoUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atOffset(ZoneOffset)} over more verbose alternatives. */
|
||||
static final class InstantAtOffset {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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 ClassCastLambdaUsageTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(ClassCastLambdaUsage.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import java.util.stream.IntStream;",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Number localVariable = 0;",
|
||||
"",
|
||||
" Stream.of(0).map(i -> i);",
|
||||
" Stream.of(1).map(i -> i + 1);",
|
||||
" Stream.of(2).map(Integer.class::cast);",
|
||||
" Stream.of(3).map(i -> (Integer) 2);",
|
||||
" Stream.of(4).map(i -> (Integer) localVariable);",
|
||||
" // XXX: Ideally this case is also flagged. Pick this up in the context of merging the",
|
||||
" // `ClassCastLambdaUsage` and `MethodReferenceUsage` checks, or introduce a separate check that",
|
||||
" // simplifies unnecessary block lambda expressions.",
|
||||
" Stream.of(5)",
|
||||
" .map(",
|
||||
" i -> {",
|
||||
" return (Integer) i;",
|
||||
" });",
|
||||
" Stream.<ImmutableSet>of(ImmutableSet.of(5)).map(s -> (ImmutableSet<Number>) s);",
|
||||
" Stream.of(ImmutableSet.of(6)).map(s -> (ImmutableSet<?>) s);",
|
||||
" Stream.of(7).reduce((a, b) -> (Integer) a);",
|
||||
" IntStream.of(8).mapToObj(i -> (char) i);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Stream.of(8).map(i -> (Integer) i);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(ClassCastLambdaUsage.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).map(i -> (Integer) i);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).map(Integer.class::cast);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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 ConstantNamingTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(ConstantNaming.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" private static final long serialVersionUID = 1L;",
|
||||
" private static final int FOO = 1;",
|
||||
" // BUG: Diagnostic contains: consider renaming to 'BAR', though note that this is not a private",
|
||||
" // constant",
|
||||
" static final int bar = 2;",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final int baz = 3;",
|
||||
" // BUG: Diagnostic contains: consider renaming to 'QUX_QUUX', though note that a variable with",
|
||||
" // this name is already declared",
|
||||
" private static final int qux_QUUX = 4;",
|
||||
" // BUG: Diagnostic contains: consider renaming to 'QUUZ', though note that a variable with",
|
||||
" // this name is already declared",
|
||||
" private static final int quuz = 3;",
|
||||
"",
|
||||
" private final int foo = 4;",
|
||||
" private final Runnable QUX_QUUX =",
|
||||
" new Runnable() {",
|
||||
" private static final int QUUZ = 1;",
|
||||
"",
|
||||
" @Override",
|
||||
" public void run() {}",
|
||||
" };",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithCustomExemption() {
|
||||
CompilationTestHelper.newInstance(ConstantNaming.class, getClass())
|
||||
.setArgs("-XepOpt:CanonicalConstantNaming:ExemptedNames=foo,baz")
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" private static final long serialVersionUID = 1L;",
|
||||
" private static final int foo = 1;",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final int bar = 2;",
|
||||
" private static final int baz = 3;",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(ConstantNaming.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" static final int foo = 1;",
|
||||
" private static final int bar = 2;",
|
||||
" private static final int baz = 3;",
|
||||
" private static final int BAZ = 4;",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" static final int foo = 1;",
|
||||
" private static final int BAR = 2;",
|
||||
" private static final int baz = 3;",
|
||||
" private static final int BAZ = 4;",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ final class IdentityConversionTest {
|
||||
"import com.google.common.collect.ImmutableTable;",
|
||||
"import com.google.errorprone.matchers.Matcher;",
|
||||
"import com.google.errorprone.matchers.Matchers;",
|
||||
"import java.time.Instant;",
|
||||
"import java.time.ZonedDateTime;",
|
||||
"import reactor.adapter.rxjava.RxJava2Adapter;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"import reactor.core.publisher.Mono;",
|
||||
@@ -149,6 +151,10 @@ final class IdentityConversionTest {
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ImmutableTable<Object, Object, Object> o11 = ImmutableTable.copyOf(ImmutableTable.of());",
|
||||
"",
|
||||
" Instant instant1 = Instant.from(ZonedDateTime.now());",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Instant instant2 = Instant.from(Instant.now());",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Matcher allOf1 = Matchers.allOf(instanceMethod());",
|
||||
" Matcher allOf2 = Matchers.allOf(instanceMethod(), staticMethod());",
|
||||
|
||||
@@ -18,22 +18,23 @@ final class IsInstanceLambdaUsageTest {
|
||||
" void m() {",
|
||||
" Integer localVariable = 0;",
|
||||
"",
|
||||
" Stream.of(0).map(i -> i + 1);",
|
||||
" Stream.of(1).filter(Integer.class::isInstance);",
|
||||
" Stream.of(2).filter(i -> i.getClass() instanceof Class);",
|
||||
" Stream.of(3).filter(i -> localVariable instanceof Integer);",
|
||||
" Stream.of(0).map(i -> i);",
|
||||
" Stream.of(1).map(i -> i + 1);",
|
||||
" Stream.of(2).filter(Integer.class::isInstance);",
|
||||
" Stream.of(3).filter(i -> i.getClass() instanceof Class);",
|
||||
" Stream.of(4).filter(i -> localVariable instanceof Integer);",
|
||||
" // XXX: Ideally this case is also flagged. Pick this up in the context of merging the",
|
||||
" // `IsInstanceLambdaUsage` and `MethodReferenceUsage` checks, or introduce a separate check that",
|
||||
" // simplifies unnecessary block lambda expressions.",
|
||||
" Stream.of(4)",
|
||||
" Stream.of(5)",
|
||||
" .filter(",
|
||||
" i -> {",
|
||||
" return localVariable instanceof Integer;",
|
||||
" return i instanceof Integer;",
|
||||
" });",
|
||||
" Flux.just(5, \"foo\").distinctUntilChanged(v -> v, (a, b) -> a instanceof Integer);",
|
||||
" Flux.just(6, \"foo\").distinctUntilChanged(v -> v, (a, b) -> a instanceof Integer);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Stream.of(6).filter(i -> i instanceof Integer);",
|
||||
" Stream.of(7).filter(i -> i instanceof Integer);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
|
||||
@@ -29,6 +29,10 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
"",
|
||||
" boolean[] bools() default {};",
|
||||
"",
|
||||
" char[] chars() default {};",
|
||||
"",
|
||||
" int[] ints() default {};",
|
||||
"",
|
||||
" Class<?>[] cls() default {};",
|
||||
@@ -69,6 +73,32 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" @Foo({\"a\", \"A\"})",
|
||||
" A unsortedStringCaseInsensitiveWithTotalOrderFallback();",
|
||||
"",
|
||||
" @Foo(bools = {})",
|
||||
" A noBools();",
|
||||
"",
|
||||
" @Foo(bools = {false})",
|
||||
" A oneBool();",
|
||||
"",
|
||||
" @Foo(bools = {false, true})",
|
||||
" A sortedBools();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(bools = {true, false})",
|
||||
" A unsortedBools();",
|
||||
"",
|
||||
" @Foo(chars = {})",
|
||||
" A noChars();",
|
||||
"",
|
||||
" @Foo(chars = {'a'})",
|
||||
" A oneChar();",
|
||||
"",
|
||||
" @Foo(chars = {'a', 'b'})",
|
||||
" A sortedChars();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(chars = {'b', 'a'})",
|
||||
" A unsortedChars();",
|
||||
"",
|
||||
" @Foo(ints = {})",
|
||||
" A noInts();",
|
||||
"",
|
||||
@@ -173,6 +203,10 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
"",
|
||||
" boolean[] bools() default {};",
|
||||
"",
|
||||
" char[] chars() default {};",
|
||||
"",
|
||||
" Class<?>[] cls() default {};",
|
||||
"",
|
||||
" RoundingMode[] enums() default {};",
|
||||
@@ -185,7 +219,13 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\" \", \"\", \"b\", \"a\"})",
|
||||
" A unsortedString();",
|
||||
" A unsortedStrings();",
|
||||
"",
|
||||
" @Foo(bools = {true, false})",
|
||||
" A unsortedBooleans();",
|
||||
"",
|
||||
" @Foo(chars = {'b', 'a'})",
|
||||
" A unsortedChars();",
|
||||
"",
|
||||
" @Foo(cls = {long.class, int.class})",
|
||||
" A unsortedClasses();",
|
||||
@@ -210,6 +250,10 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
"",
|
||||
" boolean[] bools() default {};",
|
||||
"",
|
||||
" char[] chars() default {};",
|
||||
"",
|
||||
" Class<?>[] cls() default {};",
|
||||
"",
|
||||
" RoundingMode[] enums() default {};",
|
||||
@@ -222,7 +266,13 @@ final class LexicographicalAnnotationAttributeListingTest {
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\"\", \" \", \"a\", \"b\"})",
|
||||
" A unsortedString();",
|
||||
" A unsortedStrings();",
|
||||
"",
|
||||
" @Foo(bools = {false, true})",
|
||||
" A unsortedBooleans();",
|
||||
"",
|
||||
" @Foo(chars = {'a', 'b'})",
|
||||
" A unsortedChars();",
|
||||
"",
|
||||
" @Foo(cls = {int.class, long.class})",
|
||||
" A unsortedClasses();",
|
||||
|
||||
@@ -5,14 +5,15 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class OptionalOrElseTest {
|
||||
final class OptionalOrElseGetTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(OptionalOrElse.class, getClass())
|
||||
CompilationTestHelper.newInstance(OptionalOrElseGet.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.refaster.Refaster;",
|
||||
"import java.util.Optional;",
|
||||
"import java.util.function.Supplier;",
|
||||
"",
|
||||
"class A {",
|
||||
" private final Optional<Object> optional = Optional.empty();",
|
||||
@@ -27,6 +28,7 @@ final class OptionalOrElseTest {
|
||||
" optional.orElse(string);",
|
||||
" optional.orElse(this.string);",
|
||||
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
|
||||
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Optional.empty().orElse(string + \"constant\");",
|
||||
@@ -67,7 +69,7 @@ final class OptionalOrElseTest {
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElse.class, getClass())
|
||||
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElseGet.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.Optional;",
|
||||
@@ -0,0 +1,90 @@
|
||||
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 RedundantStringEscapeTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(RedundantStringEscape.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.Arrays;",
|
||||
"import java.util.List;",
|
||||
"",
|
||||
"class A {",
|
||||
" List<String> m() {",
|
||||
" return Arrays.asList(",
|
||||
" \"foo\",",
|
||||
" \"ß\",",
|
||||
" \"'\",",
|
||||
" \"\\\"\",",
|
||||
" \"\\\\\",",
|
||||
" \"\\\\'\",",
|
||||
" \"'\\\\\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\\\\\'\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\'\\\\\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\'\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"'\\'\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\''\",",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\'\\'\",",
|
||||
" (",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" /* Leading comment. */ \"\\'\" /* Trailing comment. */),",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" \"\\'foo\\\"bar\\'baz\\\"qux\\'\");",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(RedundantStringEscape.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.Arrays;",
|
||||
"import java.util.List;",
|
||||
"",
|
||||
"class A {",
|
||||
" List<String> m() {",
|
||||
" return Arrays.asList(",
|
||||
" \"\\'\",",
|
||||
" \"'\\'\",",
|
||||
" \"\\''\",",
|
||||
" \"\\'\\'\",",
|
||||
" \"\\'ß\\'\",",
|
||||
" (",
|
||||
" /* Leading comment. */ \"\\'\" /* Trailing comment. */),",
|
||||
" \"\\'foo\\\"bar\\'baz\\\"qux\\'\");",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import java.util.Arrays;",
|
||||
"import java.util.List;",
|
||||
"",
|
||||
"class A {",
|
||||
" List<String> m() {",
|
||||
" return Arrays.asList(",
|
||||
" \"'\",",
|
||||
" \"''\",",
|
||||
" \"''\",",
|
||||
" \"''\",",
|
||||
" \"'ß'\",",
|
||||
" (",
|
||||
" /* Leading comment. */ \"'\" /* Trailing comment. */),",
|
||||
" \"'foo\\\"bar'baz\\\"qux'\");",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
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 Slf4jLoggerDeclarationTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static java.lang.Class.forName;",
|
||||
"",
|
||||
"import org.slf4j.Logger;",
|
||||
"import org.slf4j.LoggerFactory;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final long serialVersionUID = 1L;",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
|
||||
"",
|
||||
" abstract static class DynamicLogger {",
|
||||
" private final Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerWithExplicitThis {",
|
||||
" private final Logger log = LoggerFactory.getLogger(this.getClass());",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLogger {",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(StaticLogger.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithCustomIdentifier {",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" interface StaticLoggerForInterface {",
|
||||
" Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterface.class);",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerForWrongTypeWithoutReceiver {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final Logger log = LoggerFactory.getLogger(forName(\"A.class\"));",
|
||||
"",
|
||||
" DynamicLoggerForWrongTypeWithoutReceiver() throws ClassNotFoundException {}",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerForWrongTypeWithoutSymbol {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final Logger log = LoggerFactory.getLogger(\"foo\".getClass());",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerForWrongTypeWithSymbol {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final Logger log = LoggerFactory.getLogger(new A().getClass());",
|
||||
" }",
|
||||
"",
|
||||
" static final class NonAbstractDynamicLogger {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerWithMissingModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" final Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class DynamicLoggerWithExcessModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final transient Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" abstract static class MisnamedDynamicLogger {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private final Logger LOG = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithMissingModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" static final Logger LOG = LoggerFactory.getLogger(StaticLoggerWithMissingModifier.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithExcessModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final transient Logger LOG =",
|
||||
" LoggerFactory.getLogger(StaticLoggerWithExcessModifier.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class MisnamedStaticLogger {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final Logger log = LoggerFactory.getLogger(MisnamedStaticLogger.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithIncorrectIdentifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithCustomIdentifierAndMissingModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithCustomIdentifierAndExcessModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final transient Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" static final class MisnamedStaticLoggerWithCustomIdentifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" private static final Logger log = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" interface StaticLoggerForInterfaceWithExcessModifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" static Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterfaceWithExcessModifier.class);",
|
||||
" }",
|
||||
"",
|
||||
" interface MisnamedStaticLoggerForInterface {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Logger log = LoggerFactory.getLogger(MisnamedStaticLoggerForInterface.class);",
|
||||
" }",
|
||||
"",
|
||||
" interface StaticLoggerForInterfaceWithIncorrectIdentifier {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Logger LOG = LoggerFactory.getLogger(A.class);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import org.slf4j.Logger;",
|
||||
"import org.slf4j.LoggerFactory;",
|
||||
"",
|
||||
"class A {",
|
||||
" static Logger foo = LoggerFactory.getLogger(Logger.class);",
|
||||
"",
|
||||
" abstract static class DynamicLogger {",
|
||||
" transient Logger BAR = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLogger {",
|
||||
" transient Logger baz = LoggerFactory.getLogger(LoggerFactory.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithCustomIdentifier {",
|
||||
" transient Logger qux = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" interface StaticLoggerForInterface {",
|
||||
" public static final Logger quux = LoggerFactory.getLogger(A.class);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import org.slf4j.Logger;",
|
||||
"import org.slf4j.LoggerFactory;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
|
||||
"",
|
||||
" abstract static class DynamicLogger {",
|
||||
" private final Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLogger {",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(StaticLogger.class);",
|
||||
" }",
|
||||
"",
|
||||
" static final class StaticLoggerWithCustomIdentifier {",
|
||||
" private static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");",
|
||||
" }",
|
||||
"",
|
||||
" interface StaticLoggerForInterface {",
|
||||
" Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterface.class);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementWithCustomLoggerName() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass())
|
||||
.setArgs("-XepOpt:Slf4jLoggerDeclaration:CanonicalStaticLoggerName=FOO_BAR")
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import org.slf4j.Logger;",
|
||||
"import org.slf4j.LoggerFactory;",
|
||||
"",
|
||||
"class A {",
|
||||
" transient Logger LOG = LoggerFactory.getLogger(Logger.class);",
|
||||
"",
|
||||
" abstract static class DynamicLogger {",
|
||||
" transient Logger log = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import org.slf4j.Logger;",
|
||||
"import org.slf4j.LoggerFactory;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final Logger FOO_BAR = LoggerFactory.getLogger(A.class);",
|
||||
"",
|
||||
" abstract static class DynamicLogger {",
|
||||
" private final Logger fooBar = LoggerFactory.getLogger(getClass());",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ final class RefasterRulesTest {
|
||||
EqualityRules.class,
|
||||
FileRules.class,
|
||||
InputStreamRules.class,
|
||||
ImmutableEnumSetRules.class,
|
||||
ImmutableListRules.class,
|
||||
ImmutableListMultimapRules.class,
|
||||
ImmutableMapRules.class,
|
||||
@@ -58,6 +59,7 @@ final class RefasterRulesTest {
|
||||
LongStreamRules.class,
|
||||
MapEntryRules.class,
|
||||
MapRules.class,
|
||||
MicrometerRules.class,
|
||||
MockitoRules.class,
|
||||
MultimapRules.class,
|
||||
NullRules.class,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.BoundType;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -24,8 +21,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Preconditions.class,
|
||||
Sets.class,
|
||||
Splitter.class,
|
||||
Streams.class,
|
||||
toImmutableSet());
|
||||
Streams.class);
|
||||
}
|
||||
|
||||
int testCheckIndex() {
|
||||
@@ -38,10 +34,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIteratorGetNextOrDefault() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableList.of("a").iterator().hasNext()
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.toImmutableEnumSet;
|
||||
import static java.util.Objects.checkIndex;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.BoundType;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -27,8 +24,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Preconditions.class,
|
||||
Sets.class,
|
||||
Splitter.class,
|
||||
Streams.class,
|
||||
toImmutableSet());
|
||||
Streams.class);
|
||||
}
|
||||
|
||||
int testCheckIndex() {
|
||||
@@ -39,10 +35,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
checkIndex(1, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIteratorGetNextOrDefault() {
|
||||
return ImmutableSet.of(
|
||||
Iterators.getNext(ImmutableList.of("a").iterator(), "foo"),
|
||||
|
||||
@@ -4,13 +4,15 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import com.sun.tools.javac.util.Convert;
|
||||
import javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(Convert.class, FixChoosers.class);
|
||||
return ImmutableSet.of(Constants.class, Convert.class, FixChoosers.class);
|
||||
}
|
||||
|
||||
ImmutableSet<BugCheckerRefactoringTestHelper> testBugCheckerRefactoringTestHelperIdentity() {
|
||||
@@ -28,7 +30,13 @@ final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
.addOutputLines("A.java", "class A {}");
|
||||
}
|
||||
|
||||
String testConstantsFormat() {
|
||||
return String.format("\"%s\"", Convert.quote("foo"));
|
||||
ImmutableSet<String> testConstantsFormat() {
|
||||
return ImmutableSet.of(Constants.format("foo"), String.format("\"%s\"", Convert.quote("bar")));
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testNameContentEquals() {
|
||||
return ImmutableSet.of(
|
||||
((Name) null).toString().equals("foo".subSequence(0, 1).toString()),
|
||||
((com.sun.tools.javac.util.Name) null).toString().equals("bar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import com.sun.tools.javac.util.Convert;
|
||||
import javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(Convert.class, FixChoosers.class);
|
||||
return ImmutableSet.of(Constants.class, Convert.class, FixChoosers.class);
|
||||
}
|
||||
|
||||
ImmutableSet<BugCheckerRefactoringTestHelper> testBugCheckerRefactoringTestHelperIdentity() {
|
||||
@@ -27,7 +29,15 @@ final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
.expectUnchanged();
|
||||
}
|
||||
|
||||
String testConstantsFormat() {
|
||||
return Constants.format("foo");
|
||||
ImmutableSet<String> testConstantsFormat() {
|
||||
return ImmutableSet.of(
|
||||
SourceCode.toStringConstantExpression("foo", /* REPLACEME */ null),
|
||||
SourceCode.toStringConstantExpression("bar", /* REPLACEME */ null));
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testNameContentEquals() {
|
||||
return ImmutableSet.of(
|
||||
((Name) null).contentEquals("foo".subSequence(0, 1)),
|
||||
((com.sun.tools.javac.util.Name) null).contentEquals("bar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ClassRulesTest implements RefasterRuleCollectionTestCase {
|
||||
boolean testClassIsInstance() throws IOException {
|
||||
boolean testClassIsInstance() {
|
||||
return CharSequence.class.isAssignableFrom("foo".getClass());
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testInstanceof() throws IOException {
|
||||
ImmutableSet<Boolean> testInstanceof() {
|
||||
Class<?> clazz = CharSequence.class;
|
||||
return ImmutableSet.of(CharSequence.class.isInstance("foo"), clazz.isInstance("bar"));
|
||||
}
|
||||
|
||||
Predicate<String> testClassLiteralIsInstancePredicate() throws IOException {
|
||||
Predicate<String> testClassLiteralIsInstancePredicate() {
|
||||
return s -> s instanceof CharSequence;
|
||||
}
|
||||
|
||||
Predicate<String> testClassReferenceIsInstancePredicate() throws IOException {
|
||||
Predicate<String> testClassReferenceIsInstancePredicate() {
|
||||
Class<?> clazz = CharSequence.class;
|
||||
return s -> clazz.isInstance(s);
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassReferenceCast() {
|
||||
return i -> Integer.class.cast(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ClassRulesTest implements RefasterRuleCollectionTestCase {
|
||||
boolean testClassIsInstance() throws IOException {
|
||||
boolean testClassIsInstance() {
|
||||
return CharSequence.class.isInstance("foo");
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testInstanceof() throws IOException {
|
||||
ImmutableSet<Boolean> testInstanceof() {
|
||||
Class<?> clazz = CharSequence.class;
|
||||
return ImmutableSet.of("foo" instanceof CharSequence, clazz.isInstance("bar"));
|
||||
}
|
||||
|
||||
Predicate<String> testClassLiteralIsInstancePredicate() throws IOException {
|
||||
Predicate<String> testClassLiteralIsInstancePredicate() {
|
||||
return CharSequence.class::isInstance;
|
||||
}
|
||||
|
||||
Predicate<String> testClassReferenceIsInstancePredicate() throws IOException {
|
||||
Predicate<String> testClassReferenceIsInstancePredicate() {
|
||||
Class<?> clazz = CharSequence.class;
|
||||
return clazz::isInstance;
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassReferenceCast() {
|
||||
return Integer.class::cast;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Stream;
|
||||
@@ -70,6 +72,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
Stream<Integer> testSetStream() {
|
||||
return ImmutableSet.of(1).stream().distinct();
|
||||
}
|
||||
|
||||
ArrayList<String> testNewArrayListFromCollection() {
|
||||
return Lists.newArrayList(ImmutableList.of("foo"));
|
||||
}
|
||||
@@ -94,6 +100,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).asList().toString();
|
||||
}
|
||||
|
||||
List<String> testArraysAsList() {
|
||||
return Arrays.stream(new String[0]).toList();
|
||||
}
|
||||
|
||||
ImmutableSet<Object[]> testCollectionToArray() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(1).toArray(new Object[1]),
|
||||
@@ -109,8 +119,9 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).asList().toArray(Integer[]::new);
|
||||
}
|
||||
|
||||
Iterator<Integer> testImmutableCollectionIterator() {
|
||||
return ImmutableSet.of(1).asList().iterator();
|
||||
ImmutableSet<Iterator<Integer>> testCollectionIterator() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(1).stream().iterator(), ImmutableSet.of(2).asList().iterator());
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<Integer>> testOptionalFirstCollectionElement() {
|
||||
|
||||
@@ -6,9 +6,11 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Stream;
|
||||
@@ -62,6 +64,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
new HashSet<Number>().removeAll(ImmutableSet.of(2));
|
||||
}
|
||||
|
||||
Stream<Integer> testSetStream() {
|
||||
return ImmutableSet.of(1).stream();
|
||||
}
|
||||
|
||||
ArrayList<String> testNewArrayListFromCollection() {
|
||||
return new ArrayList<>(ImmutableList.of("foo"));
|
||||
}
|
||||
@@ -86,6 +92,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).toString();
|
||||
}
|
||||
|
||||
List<String> testArraysAsList() {
|
||||
return Arrays.asList(new String[0]);
|
||||
}
|
||||
|
||||
ImmutableSet<Object[]> testCollectionToArray() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(1).toArray(), ImmutableSet.of(2).toArray(), ImmutableSet.of(3).toArray());
|
||||
@@ -99,8 +109,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).toArray(Integer[]::new);
|
||||
}
|
||||
|
||||
Iterator<Integer> testImmutableCollectionIterator() {
|
||||
return ImmutableSet.of(1).iterator();
|
||||
ImmutableSet<Iterator<Integer>> testCollectionIterator() {
|
||||
return ImmutableSet.of(ImmutableSet.of(1).iterator(), ImmutableSet.of(2).iterator());
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<Integer>> testOptionalFirstCollectionElement() {
|
||||
|
||||
@@ -107,6 +107,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Comparator.<String>reverseOrder().compare("baz", "qux"));
|
||||
}
|
||||
|
||||
void testCollectionsSort() {
|
||||
Collections.sort(ImmutableList.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMin() {
|
||||
return ImmutableSet.of(
|
||||
Collections.min(ImmutableList.of("foo"), naturalOrder()),
|
||||
Collections.max(ImmutableList.of("bar"), reverseOrder()));
|
||||
}
|
||||
|
||||
String testMinOfArray() {
|
||||
return Arrays.stream(new String[0]).min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
String testCollectionsMinWithComparator() {
|
||||
return ImmutableSet.of("foo", "bar").stream().min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
int testMinOfVarargs() {
|
||||
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
@@ -135,6 +153,20 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMax() {
|
||||
return ImmutableSet.of(
|
||||
Collections.max(ImmutableList.of("foo"), naturalOrder()),
|
||||
Collections.min(ImmutableList.of("bar"), reverseOrder()));
|
||||
}
|
||||
|
||||
String testMaxOfArray() {
|
||||
return Arrays.stream(new String[0]).max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
String testCollectionsMaxWithComparator() {
|
||||
return ImmutableSet.of("foo", "bar").stream().max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
int testMaxOfVarargs() {
|
||||
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,23 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
|
||||
}
|
||||
|
||||
void testCollectionsSort() {
|
||||
Collections.sort(ImmutableList.of("foo", "bar"));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMin() {
|
||||
return ImmutableSet.of(
|
||||
Collections.min(ImmutableList.of("foo")), Collections.min(ImmutableList.of("bar")));
|
||||
}
|
||||
|
||||
String testMinOfArray() {
|
||||
return Collections.min(Arrays.asList(new String[0]), naturalOrder());
|
||||
}
|
||||
|
||||
String testCollectionsMinWithComparator() {
|
||||
return Collections.min(ImmutableSet.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
int testMinOfVarargs() {
|
||||
return Collections.min(Arrays.asList(1, 2), naturalOrder());
|
||||
}
|
||||
@@ -126,6 +143,19 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Comparators.min("a", "b", (a, b) -> 1));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMax() {
|
||||
return ImmutableSet.of(
|
||||
Collections.max(ImmutableList.of("foo")), Collections.max(ImmutableList.of("bar")));
|
||||
}
|
||||
|
||||
String testMaxOfArray() {
|
||||
return Collections.max(Arrays.asList(new String[0]), naturalOrder());
|
||||
}
|
||||
|
||||
String testCollectionsMaxWithComparator() {
|
||||
return Collections.max(ImmutableSet.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
int testMaxOfVarargs() {
|
||||
return Collections.max(Arrays.asList(1, 2), naturalOrder());
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Path testPathOfUri() {
|
||||
return Paths.get(URI.create("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<Path> testPathOfString() {
|
||||
return ImmutableSet.of(Paths.get("foo"), Paths.get("bar", "baz", "qux"));
|
||||
}
|
||||
|
||||
Path testPathInstance() {
|
||||
return Path.of("foo").toFile().toPath();
|
||||
}
|
||||
|
||||
String testFilesReadStringWithCharset() throws IOException {
|
||||
return new String(Files.readAllBytes(Paths.get("foo")), StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
@@ -14,4 +30,13 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
String testFilesReadString() throws IOException {
|
||||
return Files.readString(Paths.get("foo"), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
ImmutableSet<File> testFilesCreateTempFileToFile() throws IOException {
|
||||
return ImmutableSet.of(
|
||||
File.createTempFile("foo", "bar"), File.createTempFile("baz", "qux", null));
|
||||
}
|
||||
|
||||
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
|
||||
return File.createTempFile("foo", "bar", new File("baz"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Path testPathOfUri() {
|
||||
return Path.of(URI.create("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<Path> testPathOfString() {
|
||||
return ImmutableSet.of(Path.of("foo"), Path.of("bar", "baz", "qux"));
|
||||
}
|
||||
|
||||
Path testPathInstance() {
|
||||
return Path.of("foo");
|
||||
}
|
||||
|
||||
String testFilesReadStringWithCharset() throws IOException {
|
||||
return Files.readString(Paths.get("foo"), StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
@@ -14,4 +30,13 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
String testFilesReadString() throws IOException {
|
||||
return Files.readString(Paths.get("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<File> testFilesCreateTempFileToFile() throws IOException {
|
||||
return ImmutableSet.of(
|
||||
Files.createTempFile("foo", "bar").toFile(), Files.createTempFile("baz", "qux").toFile());
|
||||
}
|
||||
|
||||
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
|
||||
return Files.createTempFile(new File("baz").toPath(), "foo", "bar").toFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
|
||||
import com.google.common.collect.BoundType;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.EnumSet;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ImmutableEnumSetRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(EnumSet.class, toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSetIterable() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.copyOf(Iterables.cycle(RoundingMode.UP)),
|
||||
ImmutableSet.copyOf(EnumSet.allOf(RoundingMode.class)));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetArraysAsList() {
|
||||
return ImmutableSet.copyOf(RoundingMode.values());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet1() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP), ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet2() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN),
|
||||
ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet3() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING),
|
||||
ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet4() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(
|
||||
RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR),
|
||||
ImmutableSet.copyOf(
|
||||
EnumSet.of(
|
||||
RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet5() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY),
|
||||
ImmutableSet.copyOf(
|
||||
EnumSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY)));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSet6() {
|
||||
return ImmutableSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetVarArgs() {
|
||||
return ImmutableSet.copyOf(
|
||||
EnumSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.toImmutableEnumSet;
|
||||
|
||||
import com.google.common.collect.BoundType;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ImmutableEnumSetRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(EnumSet.class, toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSetIterable() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(Iterables.cycle(RoundingMode.UP)),
|
||||
Sets.immutableEnumSet(EnumSet.allOf(RoundingMode.class)));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetArraysAsList() {
|
||||
return Sets.immutableEnumSet(Arrays.asList(RoundingMode.values()));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet1() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP), Sets.immutableEnumSet(RoundingMode.UP));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet2() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN),
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet3() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING),
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet4() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(
|
||||
RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR),
|
||||
Sets.immutableEnumSet(
|
||||
RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet5() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY),
|
||||
Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSet6() {
|
||||
return Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetVarArgs() {
|
||||
return Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
|
||||
Stream.<Integer>empty().collect(toImmutableMultiset()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableList.of(1).stream().collect(toImmutableMultiset()),
|
||||
|
||||
@@ -24,6 +24,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
|
||||
return ImmutableMultiset.of(ImmutableMultiset.of(), ImmutableMultiset.of());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableMultiset.copyOf(ImmutableList.of(1)),
|
||||
|
||||
@@ -37,6 +37,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
|
||||
Stream.<Integer>empty().collect(toImmutableSortedMultiset(naturalOrder())));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableSortedMultiset.copyOf(naturalOrder(), ImmutableList.of(1)),
|
||||
|
||||
@@ -35,6 +35,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
|
||||
return ImmutableMultiset.of(ImmutableSortedMultiset.of(), ImmutableSortedMultiset.of());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableSortedMultiset.copyOf(ImmutableList.of(1)),
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class MicrometerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(ImmutableList.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf1() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(Tag.of("foo", "v1")), ImmutableList.of(Tag.of("bar", "v2")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf2() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(Tag.of("foo", "v1"), Tag.of("bar", "v2")),
|
||||
ImmutableList.of(Tag.of("baz", "v3"), Tag.of("qux", "v4")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf3() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3")),
|
||||
ImmutableList.of(Tag.of("qux", "v4"), Tag.of("quux", "v5"), Tag.of("corge", "v6")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf4() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(
|
||||
Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3"), Tag.of("qux", "v4")),
|
||||
ImmutableList.of(
|
||||
Tag.of("quux", "v5"),
|
||||
Tag.of("corge", "v6"),
|
||||
Tag.of("grault", "v7"),
|
||||
Tag.of("garply", "v8")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf5() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(
|
||||
Tag.of("foo", "v1"),
|
||||
Tag.of("bar", "v2"),
|
||||
Tag.of("baz", "v3"),
|
||||
Tag.of("qux", "v4"),
|
||||
Tag.of("quux", "v5")),
|
||||
ImmutableList.of(
|
||||
Tag.of("corge", "v6"),
|
||||
Tag.of("grault", "v7"),
|
||||
Tag.of("garply", "v8"),
|
||||
Tag.of("waldo", "v9"),
|
||||
Tag.of("fred", "v10")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class MicrometerRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(ImmutableList.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf1() {
|
||||
return ImmutableSet.of(Tags.of(Tag.of("foo", "v1")), Tags.of(Tag.of("bar", "v2")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf2() {
|
||||
return ImmutableSet.of(
|
||||
Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2")),
|
||||
Tags.of(Tag.of("baz", "v3"), Tag.of("qux", "v4")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf3() {
|
||||
return ImmutableSet.of(
|
||||
Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3")),
|
||||
Tags.of(Tag.of("qux", "v4"), Tag.of("quux", "v5"), Tag.of("corge", "v6")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf4() {
|
||||
return ImmutableSet.of(
|
||||
Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3"), Tag.of("qux", "v4")),
|
||||
Tags.of(
|
||||
Tag.of("quux", "v5"),
|
||||
Tag.of("corge", "v6"),
|
||||
Tag.of("grault", "v7"),
|
||||
Tag.of("garply", "v8")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf5() {
|
||||
return ImmutableSet.of(
|
||||
Tags.of(
|
||||
Tag.of("foo", "v1"),
|
||||
Tag.of("bar", "v2"),
|
||||
Tag.of("baz", "v3"),
|
||||
Tag.of("qux", "v4"),
|
||||
Tag.of("quux", "v5")),
|
||||
Tags.of(
|
||||
Tag.of("corge", "v6"),
|
||||
Tag.of("grault", "v7"),
|
||||
Tag.of("garply", "v8"),
|
||||
Tag.of("waldo", "v9"),
|
||||
Tag.of("fred", "v10")));
|
||||
}
|
||||
}
|
||||
@@ -83,11 +83,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Optional.of("foo").orElseGet(() -> Optional.of("bar").orElseThrow());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testOptionalOrElseGet() {
|
||||
ImmutableSet<String> testOptionalOrElse() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").orElse("bar"),
|
||||
Optional.of("baz").orElse(toString()),
|
||||
Optional.of("qux").orElse(String.valueOf(true)));
|
||||
Optional.of("foo").orElseGet(() -> "bar"), Optional.of("baz").orElseGet(() -> toString()));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testStreamFlatMapOptional() {
|
||||
|
||||
@@ -80,11 +80,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Optional.of("foo").or(() -> Optional.of("bar")).orElseThrow();
|
||||
}
|
||||
|
||||
ImmutableSet<String> testOptionalOrElseGet() {
|
||||
ImmutableSet<String> testOptionalOrElse() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").orElse("bar"),
|
||||
Optional.of("baz").orElse(toString()),
|
||||
Optional.of("qux").orElseGet(() -> String.valueOf(true)));
|
||||
Optional.of("foo").orElse("bar"), Optional.of("baz").orElseGet(() -> toString()));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testStreamFlatMapOptional() {
|
||||
|
||||
@@ -9,6 +9,8 @@ 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.common.primitives.UnsignedInts;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Floats.class,
|
||||
Ints.class,
|
||||
Longs.class,
|
||||
Shorts.class);
|
||||
Shorts.class,
|
||||
UnsignedInts.class,
|
||||
UnsignedLongs.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testLessThan() {
|
||||
@@ -105,34 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Doubles.hashCode(1);
|
||||
}
|
||||
|
||||
int testBooleanCompare() {
|
||||
return Booleans.compare(false, true);
|
||||
}
|
||||
|
||||
int testCharacterCompare() {
|
||||
return Chars.compare('a', 'b');
|
||||
}
|
||||
|
||||
int testShortCompare() {
|
||||
return Shorts.compare((short) 1, (short) 2);
|
||||
}
|
||||
|
||||
int testIntegerCompare() {
|
||||
return Ints.compare(1, 2);
|
||||
}
|
||||
|
||||
int testLongCompare() {
|
||||
return Longs.compare(1, 2);
|
||||
}
|
||||
|
||||
int testFloatCompare() {
|
||||
return Floats.compare(1, 2);
|
||||
}
|
||||
|
||||
int testDoubleCompare() {
|
||||
return Doubles.compare(1, 2);
|
||||
}
|
||||
|
||||
int testCharacterBytes() {
|
||||
return Chars.BYTES;
|
||||
}
|
||||
@@ -190,4 +166,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
Long.signum(1L) < 0, Long.signum(2L) <= -1, Long.signum(3L) >= 0, Long.signum(4L) > -1);
|
||||
}
|
||||
|
||||
int testIntegerCompareUnsigned() {
|
||||
return UnsignedInts.compare(1, 2);
|
||||
}
|
||||
|
||||
long testLongCompareUnsigned() {
|
||||
return UnsignedLongs.compare(1, 2);
|
||||
}
|
||||
|
||||
int testIntegerDivideUnsigned() {
|
||||
return UnsignedInts.divide(1, 2);
|
||||
}
|
||||
|
||||
long testLongDivideUnsigned() {
|
||||
return UnsignedLongs.divide(1, 2);
|
||||
}
|
||||
|
||||
int testIntegerRemainderUnsigned() {
|
||||
return UnsignedInts.remainder(1, 2);
|
||||
}
|
||||
|
||||
long testLongRemainderUnsigned() {
|
||||
return UnsignedLongs.remainder(1, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
|
||||
return ImmutableSet.of(UnsignedInts.parseUnsignedInt("1"), Integer.parseUnsignedInt("2", 10));
|
||||
}
|
||||
|
||||
ImmutableSet<Long> testLongParseUnsignedLong() {
|
||||
return ImmutableSet.of(UnsignedLongs.parseUnsignedLong("1"), Long.parseUnsignedLong("2", 10));
|
||||
}
|
||||
|
||||
int testIntegerParseUnsignedIntWithRadix() {
|
||||
return UnsignedInts.parseUnsignedInt("1", 2);
|
||||
}
|
||||
|
||||
long testLongParseUnsignedLongWithRadix() {
|
||||
return UnsignedLongs.parseUnsignedLong("1", 2);
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIntegerToUnsignedString() {
|
||||
return ImmutableSet.of(UnsignedInts.toString(1), Integer.toUnsignedString(2, 10));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testLongToUnsignedString() {
|
||||
return ImmutableSet.of(UnsignedLongs.toString(1), Long.toUnsignedString(2, 10));
|
||||
}
|
||||
|
||||
String testIntegerToUnsignedStringWithRadix() {
|
||||
return UnsignedInts.toString(1, 2);
|
||||
}
|
||||
|
||||
String testLongToUnsignedStringWithRadix() {
|
||||
return UnsignedLongs.toString(1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ 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.common.primitives.UnsignedInts;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Floats.class,
|
||||
Ints.class,
|
||||
Longs.class,
|
||||
Shorts.class);
|
||||
Shorts.class,
|
||||
UnsignedInts.class,
|
||||
UnsignedLongs.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testLessThan() {
|
||||
@@ -105,34 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Double.hashCode(1);
|
||||
}
|
||||
|
||||
int testBooleanCompare() {
|
||||
return Boolean.compare(false, true);
|
||||
}
|
||||
|
||||
int testCharacterCompare() {
|
||||
return Character.compare('a', 'b');
|
||||
}
|
||||
|
||||
int testShortCompare() {
|
||||
return Short.compare((short) 1, (short) 2);
|
||||
}
|
||||
|
||||
int testIntegerCompare() {
|
||||
return Integer.compare(1, 2);
|
||||
}
|
||||
|
||||
int testLongCompare() {
|
||||
return Long.compare(1, 2);
|
||||
}
|
||||
|
||||
int testFloatCompare() {
|
||||
return Float.compare(1, 2);
|
||||
}
|
||||
|
||||
int testDoubleCompare() {
|
||||
return Double.compare(1, 2);
|
||||
}
|
||||
|
||||
int testCharacterBytes() {
|
||||
return Character.BYTES;
|
||||
}
|
||||
@@ -190,4 +166,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
Long.signum(1L) == -1, Long.signum(2L) == -1, Long.signum(3L) != -1, Long.signum(4L) != -1);
|
||||
}
|
||||
|
||||
int testIntegerCompareUnsigned() {
|
||||
return Integer.compareUnsigned(1, 2);
|
||||
}
|
||||
|
||||
long testLongCompareUnsigned() {
|
||||
return Long.compareUnsigned(1, 2);
|
||||
}
|
||||
|
||||
int testIntegerDivideUnsigned() {
|
||||
return Integer.divideUnsigned(1, 2);
|
||||
}
|
||||
|
||||
long testLongDivideUnsigned() {
|
||||
return Long.divideUnsigned(1, 2);
|
||||
}
|
||||
|
||||
int testIntegerRemainderUnsigned() {
|
||||
return Integer.remainderUnsigned(1, 2);
|
||||
}
|
||||
|
||||
long testLongRemainderUnsigned() {
|
||||
return Long.remainderUnsigned(1, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
|
||||
return ImmutableSet.of(Integer.parseUnsignedInt("1"), Integer.parseUnsignedInt("2"));
|
||||
}
|
||||
|
||||
ImmutableSet<Long> testLongParseUnsignedLong() {
|
||||
return ImmutableSet.of(Long.parseUnsignedLong("1"), Long.parseUnsignedLong("2"));
|
||||
}
|
||||
|
||||
int testIntegerParseUnsignedIntWithRadix() {
|
||||
return Integer.parseUnsignedInt("1", 2);
|
||||
}
|
||||
|
||||
long testLongParseUnsignedLongWithRadix() {
|
||||
return Long.parseUnsignedLong("1", 2);
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIntegerToUnsignedString() {
|
||||
return ImmutableSet.of(Integer.toUnsignedString(1), Integer.toUnsignedString(2));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testLongToUnsignedString() {
|
||||
return ImmutableSet.of(Long.toUnsignedString(1), Long.toUnsignedString(2));
|
||||
}
|
||||
|
||||
String testIntegerToUnsignedStringWithRadix() {
|
||||
return Integer.toUnsignedString(1, 2);
|
||||
}
|
||||
|
||||
String testLongToUnsignedStringWithRadix() {
|
||||
return Long.toUnsignedString(1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import static java.util.stream.Collectors.minBy;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -23,6 +24,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.math.MathFlux;
|
||||
@@ -434,8 +436,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxFromIterable() {
|
||||
return Flux.fromStream(ImmutableList.of("foo").stream());
|
||||
ImmutableSet<Flux<String>> testFluxFromIterable() {
|
||||
return ImmutableSet.of(
|
||||
Flux.fromStream(ImmutableList.of("foo")::stream),
|
||||
Flux.fromStream(() -> ImmutableList.of("bar").stream()));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testFluxCountMapMathToIntExact() {
|
||||
@@ -594,6 +598,18 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return StepVerifier.create(Flux.just(1));
|
||||
}
|
||||
|
||||
Object testStepVerifierVerify() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verifyThenAssertThat();
|
||||
}
|
||||
|
||||
Object testStepVerifierVerifyDuration() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verifyThenAssertThat(Duration.ZERO);
|
||||
}
|
||||
|
||||
StepVerifier testStepVerifierVerifyLater() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verifyLater().verifyLater();
|
||||
}
|
||||
|
||||
ImmutableSet<StepVerifier.Step<Integer>> testStepVerifierStepIdentity() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).as(StepVerifier::create).expectNext(),
|
||||
@@ -634,17 +650,43 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
.verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(AssertionError.class)));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorMatches() {
|
||||
return Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectErrorMatches(IllegalArgumentException.class::equals)
|
||||
.verify();
|
||||
ImmutableSet<?> testStepVerifierLastStepVerifyErrorMatches() {
|
||||
return ImmutableSet.of(
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectErrorMatches(IllegalArgumentException.class::equals)
|
||||
.verify(),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectError()
|
||||
.verifyThenAssertThat()
|
||||
.hasOperatorErrorMatching(IllegalStateException.class::equals));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorSatisfies() {
|
||||
return Mono.empty().as(StepVerifier::create).expectErrorSatisfies(t -> {}).verify();
|
||||
}
|
||||
|
||||
ImmutableSet<?> testStepVerifierLastStepVerifyErrorSatisfiesAssertJ() {
|
||||
return ImmutableSet.of(
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectError()
|
||||
.verifyThenAssertThat()
|
||||
.hasOperatorErrorOfType(IllegalArgumentException.class)
|
||||
.hasOperatorErrorWithMessage("foo"),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectError(IllegalStateException.class)
|
||||
.verifyThenAssertThat()
|
||||
.hasOperatorErrorWithMessage("bar"),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.expectErrorMessage("baz")
|
||||
.verifyThenAssertThat()
|
||||
.hasOperatorErrorOfType(AssertionError.class));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorMessage() {
|
||||
return Mono.empty().as(StepVerifier::create).expectErrorMessage("foo").verify();
|
||||
}
|
||||
@@ -660,4 +702,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Mono<Void> testMonoFromFutureSupplierBoolean() {
|
||||
return Mono.fromFuture(CompletableFuture.completedFuture(null), true);
|
||||
}
|
||||
|
||||
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
|
||||
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxFromStreamSupplier() {
|
||||
return Flux.fromStream(Stream.of(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -25,6 +26,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.function.TupleUtils;
|
||||
@@ -429,8 +431,9 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxFromIterable() {
|
||||
return Flux.fromIterable(ImmutableList.of("foo"));
|
||||
ImmutableSet<Flux<String>> testFluxFromIterable() {
|
||||
return ImmutableSet.of(
|
||||
Flux.fromIterable(ImmutableList.of("foo")), Flux.fromIterable(ImmutableList.of("bar")));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testFluxCountMapMathToIntExact() {
|
||||
@@ -583,6 +586,18 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Flux.just(1).as(StepVerifier::create);
|
||||
}
|
||||
|
||||
Object testStepVerifierVerify() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verify();
|
||||
}
|
||||
|
||||
Object testStepVerifierVerifyDuration() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verify(Duration.ZERO);
|
||||
}
|
||||
|
||||
StepVerifier testStepVerifierVerifyLater() {
|
||||
return Mono.empty().as(StepVerifier::create).expectError().verifyLater();
|
||||
}
|
||||
|
||||
ImmutableSet<StepVerifier.Step<Integer>> testStepVerifierStepIdentity() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).as(StepVerifier::create),
|
||||
@@ -616,16 +631,36 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Mono.empty().as(StepVerifier::create).verifyError(AssertionError.class));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorMatches() {
|
||||
return Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorMatches(IllegalArgumentException.class::equals);
|
||||
ImmutableSet<?> testStepVerifierLastStepVerifyErrorMatches() {
|
||||
return ImmutableSet.of(
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorMatches(IllegalArgumentException.class::equals),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorMatches(IllegalStateException.class::equals));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorSatisfies() {
|
||||
return Mono.empty().as(StepVerifier::create).verifyErrorSatisfies(t -> {});
|
||||
}
|
||||
|
||||
ImmutableSet<?> testStepVerifierLastStepVerifyErrorSatisfiesAssertJ() {
|
||||
return ImmutableSet.of(
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorSatisfies(
|
||||
t -> assertThat(t).isInstanceOf(IllegalArgumentException.class).hasMessage("foo")),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorSatisfies(
|
||||
t -> assertThat(t).isInstanceOf(IllegalStateException.class).hasMessage("bar")),
|
||||
Mono.empty()
|
||||
.as(StepVerifier::create)
|
||||
.verifyErrorSatisfies(
|
||||
t -> assertThat(t).isInstanceOf(AssertionError.class).hasMessage("baz")));
|
||||
}
|
||||
|
||||
Duration testStepVerifierLastStepVerifyErrorMessage() {
|
||||
return Mono.empty().as(StepVerifier::create).verifyErrorMessage("foo");
|
||||
}
|
||||
@@ -641,4 +676,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Mono<Void> testMonoFromFutureSupplierBoolean() {
|
||||
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true);
|
||||
}
|
||||
|
||||
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
|
||||
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0), true);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxFromStreamSupplier() {
|
||||
return Flux.fromStream(() -> Stream.of(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,32 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
|
||||
int testUtf8EncodedLength() {
|
||||
return "foo".getBytes(UTF_8).length;
|
||||
}
|
||||
|
||||
int testStringIndexOfChar() {
|
||||
return "foo".substring(1).indexOf('a');
|
||||
}
|
||||
|
||||
int testStringIndexOfString() {
|
||||
return "foo".substring(1).indexOf("bar");
|
||||
}
|
||||
|
||||
int testStringLastIndexOfChar() {
|
||||
return "foo".substring(1).lastIndexOf('a');
|
||||
}
|
||||
|
||||
int testStringLastIndexOfString() {
|
||||
return "foo".substring(1).lastIndexOf("bar");
|
||||
}
|
||||
|
||||
int testStringLastIndexOfCharWithIndex() {
|
||||
return "foo".substring(0, 2).lastIndexOf('a');
|
||||
}
|
||||
|
||||
int testStringLastIndexOfStringWithIndex() {
|
||||
return "foo".substring(0, 2).lastIndexOf("bar");
|
||||
}
|
||||
|
||||
boolean testStringStartsWith() {
|
||||
return "foo".substring(1).startsWith("bar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,32 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase {
|
||||
int testUtf8EncodedLength() {
|
||||
return Utf8.encodedLength("foo");
|
||||
}
|
||||
|
||||
int testStringIndexOfChar() {
|
||||
return Math.max(-1, "foo".indexOf('a', 1) - 1);
|
||||
}
|
||||
|
||||
int testStringIndexOfString() {
|
||||
return Math.max(-1, "foo".indexOf("bar", 1) - 1);
|
||||
}
|
||||
|
||||
int testStringLastIndexOfChar() {
|
||||
return Math.max(-1, "foo".lastIndexOf('a') - 1);
|
||||
}
|
||||
|
||||
int testStringLastIndexOfString() {
|
||||
return Math.max(-1, "foo".lastIndexOf("bar") - 1);
|
||||
}
|
||||
|
||||
int testStringLastIndexOfCharWithIndex() {
|
||||
return "foo".lastIndexOf('a', 2 - 1);
|
||||
}
|
||||
|
||||
int testStringLastIndexOfStringWithIndex() {
|
||||
return "foo".lastIndexOf("bar", 2 - 1);
|
||||
}
|
||||
|
||||
boolean testStringStartsWith() {
|
||||
return "foo".startsWith("bar", 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,32 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Instant.EPOCH.atZone(ZoneOffset.UTC).toOffsetDateTime();
|
||||
}
|
||||
|
||||
ImmutableSet<Instant> testInstantIdentity() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.plusMillis(1).plus(Duration.ZERO),
|
||||
Instant.EPOCH.plusMillis(2).plus(0, ChronoUnit.MILLIS),
|
||||
Instant.EPOCH.plusMillis(3).plusNanos(0L),
|
||||
Instant.EPOCH.plusMillis(4).plusMillis(0),
|
||||
Instant.EPOCH.plusMillis(5).plusSeconds(0L),
|
||||
Instant.EPOCH.plusMillis(6).minus(Duration.ZERO),
|
||||
Instant.EPOCH.plusMillis(7).minus(0, ChronoUnit.SECONDS),
|
||||
Instant.EPOCH.plusMillis(8).minusNanos(0L),
|
||||
Instant.EPOCH.plusMillis(9).minusMillis(0),
|
||||
Instant.EPOCH.plusMillis(10).minusSeconds(0L),
|
||||
Instant.parse(Instant.EPOCH.plusMillis(11).toString()),
|
||||
Instant.EPOCH.plusMillis(12).truncatedTo(ChronoUnit.NANOS),
|
||||
Instant.ofEpochSecond(
|
||||
Instant.EPOCH.plusMillis(13).getEpochSecond(), Instant.EPOCH.plusMillis(13).getNano()));
|
||||
}
|
||||
|
||||
Instant testInstantTruncatedToMilliseconds() {
|
||||
return Instant.ofEpochMilli(Instant.EPOCH.toEpochMilli());
|
||||
}
|
||||
|
||||
Instant testInstantTruncatedToSeconds() {
|
||||
return Instant.ofEpochSecond(Instant.EPOCH.getEpochSecond());
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,31 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<Instant> testInstantIdentity() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.plusMillis(1),
|
||||
Instant.EPOCH.plusMillis(2),
|
||||
Instant.EPOCH.plusMillis(3),
|
||||
Instant.EPOCH.plusMillis(4),
|
||||
Instant.EPOCH.plusMillis(5),
|
||||
Instant.EPOCH.plusMillis(6),
|
||||
Instant.EPOCH.plusMillis(7),
|
||||
Instant.EPOCH.plusMillis(8),
|
||||
Instant.EPOCH.plusMillis(9),
|
||||
Instant.EPOCH.plusMillis(10),
|
||||
Instant.EPOCH.plusMillis(11),
|
||||
Instant.EPOCH.plusMillis(12),
|
||||
Instant.EPOCH.plusMillis(13));
|
||||
}
|
||||
|
||||
Instant testInstantTruncatedToMilliseconds() {
|
||||
return Instant.EPOCH.truncatedTo(ChronoUnit.MILLIS);
|
||||
}
|
||||
|
||||
Instant testInstantTruncatedToSeconds() {
|
||||
return Instant.EPOCH.truncatedTo(ChronoUnit.SECONDS);
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return Instant.EPOCH.atOffset(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-experimental</artifactId>
|
||||
|
||||
@@ -73,19 +73,18 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
* because the latter are not syntactically valid or ambiguous. Rather than encoding all these
|
||||
* edge cases we try to compile the code with the suggested fix, to see whether this works.
|
||||
*/
|
||||
return constructMethodRef(tree, tree.getBody())
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.filter(
|
||||
fix ->
|
||||
SuggestedFixes.compilesWithFix(
|
||||
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true))
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
SuggestedFix fix = constructMethodRef(tree, tree.getBody());
|
||||
if (fix.isEmpty()
|
||||
|| !SuggestedFixes.compilesWithFix(
|
||||
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, Tree subTree) {
|
||||
private static SuggestedFix constructMethodRef(LambdaExpressionTree lambdaExpr, Tree subTree) {
|
||||
return switch (subTree.getKind()) {
|
||||
case BLOCK -> constructMethodRef(lambdaExpr, (BlockTree) subTree);
|
||||
case EXPRESSION_STATEMENT ->
|
||||
@@ -94,27 +93,28 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
case PARENTHESIZED ->
|
||||
constructMethodRef(lambdaExpr, ((ParenthesizedTree) subTree).getExpression());
|
||||
case RETURN -> constructMethodRef(lambdaExpr, ((ReturnTree) subTree).getExpression());
|
||||
default -> Optional.empty();
|
||||
default -> SuggestedFix.emptyFix();
|
||||
};
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
private static SuggestedFix constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, BlockTree subTree) {
|
||||
return Optional.of(subTree.getStatements())
|
||||
.filter(statements -> statements.size() == 1)
|
||||
.flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0)));
|
||||
.map(statements -> constructMethodRef(lambdaExpr, statements.get(0)))
|
||||
.orElseGet(SuggestedFix::emptyFix);
|
||||
}
|
||||
|
||||
// XXX: Replace nested `Optional` usage.
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
private static SuggestedFix constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
|
||||
return matchArguments(lambdaExpr, subTree)
|
||||
.flatMap(expectedInstance -> constructMethodRef(lambdaExpr, subTree, expectedInstance));
|
||||
.map(expectedInstance -> constructMethodRef(lambdaExpr, subTree, expectedInstance))
|
||||
.orElseGet(SuggestedFix::emptyFix);
|
||||
}
|
||||
|
||||
// XXX: Review whether to use switch pattern matching once the targeted JDK supports this.
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
private static SuggestedFix constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr,
|
||||
MethodInvocationTree subTree,
|
||||
Optional<Name> expectedInstance) {
|
||||
@@ -123,7 +123,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
if (methodSelect instanceof IdentifierTree) {
|
||||
if (expectedInstance.isPresent()) {
|
||||
/* Direct method call; there is no matching "implicit parameter". */
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
Symbol sym = ASTHelpers.getSymbol(methodSelect);
|
||||
@@ -139,7 +139,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind());
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
private static SuggestedFix constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, MemberSelectTree subTree, Optional<Name> expectedInstance) {
|
||||
if (!(subTree.getExpression() instanceof IdentifierTree identifier)) {
|
||||
// XXX: Could be parenthesized. Handle. Also in other classes.
|
||||
@@ -147,7 +147,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
* Only suggest a replacement if the method select's expression provably doesn't have
|
||||
* side-effects. Otherwise the replacement may not be behavior preserving.
|
||||
*/
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
Name lhs = identifier.getName();
|
||||
@@ -157,7 +157,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
|
||||
Type lhsType = ASTHelpers.getType(identifier);
|
||||
if (lhsType == null || !expectedInstance.orElseThrow().equals(lhs)) {
|
||||
return Optional.empty();
|
||||
return SuggestedFix.emptyFix();
|
||||
}
|
||||
|
||||
// XXX: Dropping generic type information is in most cases fine or even more likely to yield a
|
||||
@@ -195,23 +195,23 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
|
||||
// XXX: Resolve this suppression.
|
||||
@SuppressWarnings("UnqualifiedSuggestedFixImport")
|
||||
private static Optional<SuggestedFix.Builder> constructFix(
|
||||
private static SuggestedFix constructFix(
|
||||
LambdaExpressionTree lambdaExpr, Symbol target, Object methodName) {
|
||||
Name sName = target.getSimpleName();
|
||||
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
|
||||
SuggestedFix fix = constructFix(lambdaExpr, sName, methodName);
|
||||
|
||||
if (!"java.lang".equals(ASTHelpers.enclosingPackage(target).toString())) {
|
||||
Name fqName = target.getQualifiedName();
|
||||
if (!sName.equals(fqName)) {
|
||||
return fix.map(b -> b.addImport(fqName.toString()));
|
||||
return fix.toBuilder().addImport(fqName.toString()).build();
|
||||
}
|
||||
}
|
||||
|
||||
return fix;
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> constructFix(
|
||||
private static SuggestedFix constructFix(
|
||||
LambdaExpressionTree lambdaExpr, Object target, Object methodName) {
|
||||
return Optional.of(SuggestedFix.builder().replace(lambdaExpr, target + "::" + methodName));
|
||||
return SuggestedFix.replace(lambdaExpr, target + "::" + methodName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-guidelines</artifactId>
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.sun.source.tree.BinaryTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link BugChecker} declarations inside {@code
|
||||
@@ -126,7 +126,9 @@ public final class BugPatternLink extends BugChecker implements ClassTreeMatcher
|
||||
state,
|
||||
"link",
|
||||
ImmutableList.of(
|
||||
linkPrefix + " + " + Constants.format(tree.getSimpleName().toString()))));
|
||||
linkPrefix
|
||||
+ " + "
|
||||
+ SourceCode.toStringConstantExpression(tree.getSimpleName(), state))));
|
||||
|
||||
String linkType =
|
||||
SuggestedFixes.qualifyStaticImport(
|
||||
|
||||
@@ -26,8 +26,8 @@ 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.utils.SourceCode;
|
||||
import tech.picnic.errorprone.utils.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
@@ -123,7 +123,8 @@ public final class ErrorProneRuntimeClasspath extends BugChecker
|
||||
.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())))
|
||||
tree,
|
||||
SourceCode.toStringConstantExpression(receiver.owner.getQualifiedName(), state)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -150,7 +151,9 @@ public final class ErrorProneRuntimeClasspath extends BugChecker
|
||||
original,
|
||||
identifier
|
||||
+ ".class.getCanonicalName()"
|
||||
+ (suffix.isEmpty() ? "" : (" + " + Constants.format(suffix))))
|
||||
+ (suffix.isEmpty()
|
||||
? ""
|
||||
: (" + " + SourceCode.toStringConstantExpression(suffix, state))))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import com.sun.tools.javac.code.Attribute;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.TypeSymbol;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -49,6 +48,7 @@ import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that validates the claim made by {@link
|
||||
@@ -129,7 +129,9 @@ public final class ExhaustiveRefasterTypeMigration extends BugChecker implements
|
||||
migrationAnnotation,
|
||||
state,
|
||||
TYPE_MIGRATION_UNMIGRATED_METHODS_ELEMENT,
|
||||
unmigratedMethods.stream().map(Constants::format).collect(toImmutableList()))
|
||||
unmigratedMethods.stream()
|
||||
.map(m -> SourceCode.toStringConstantExpression(m, state))
|
||||
.collect(toImmutableList()))
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.lang.model.element.Name;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
@@ -74,12 +73,11 @@ public final class RefasterMethodParameterOrder extends BugChecker implements Cl
|
||||
|
||||
Comparator<VariableTree> canonicalOrder = determineCanonicalParameterOrder(methods);
|
||||
|
||||
return methods.stream()
|
||||
.flatMap(m -> tryReorderParameters(m, canonicalOrder, state))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
SuggestedFix fix =
|
||||
methods.stream()
|
||||
.map(m -> tryReorderParameters(m, canonicalOrder, state))
|
||||
.collect(SuggestedFix.mergeFixes());
|
||||
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
private static ImmutableList<MethodTree> getMethodsByPriority(
|
||||
@@ -123,17 +121,18 @@ public final class RefasterMethodParameterOrder extends BugChecker implements Cl
|
||||
}.scan(method, null);
|
||||
}
|
||||
|
||||
private static Stream<SuggestedFix.Builder> tryReorderParameters(
|
||||
private static SuggestedFix tryReorderParameters(
|
||||
MethodTree method, Comparator<VariableTree> canonicalOrder, VisitorState state) {
|
||||
List<? extends VariableTree> originalOrder = method.getParameters();
|
||||
ImmutableList<? extends VariableTree> orderedParams =
|
||||
ImmutableList.sortedCopyOf(canonicalOrder, originalOrder);
|
||||
|
||||
return originalOrder.equals(orderedParams)
|
||||
? Stream.empty()
|
||||
? SuggestedFix.emptyFix()
|
||||
: Streams.zip(
|
||||
originalOrder.stream(),
|
||||
orderedParams.stream().map(p -> SourceCode.treeToString(p, state)),
|
||||
SuggestedFix.builder()::replace);
|
||||
originalOrder.stream(),
|
||||
orderedParams.stream().map(p -> SourceCode.treeToString(p, state)),
|
||||
SuggestedFix::replace)
|
||||
.collect(SuggestedFix.mergeFixes());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
|
||||
/** Helper methods for working with {@link ErrorProneFlags}. */
|
||||
@@ -19,4 +20,19 @@ public final class Flags {
|
||||
ImmutableList<String> list = errorProneFlags.getListOrEmpty(name);
|
||||
return list.equals(ImmutableList.of("")) ? ImmutableList.of() : list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of (comma-separated) arguments passed using the given Error Prone flag.
|
||||
*
|
||||
* @param errorProneFlags The full set of flags provided.
|
||||
* @param name The name of the flag of interest.
|
||||
* @return A non-{@code null} set of provided arguments; this set is empty if the flag was not
|
||||
* provided, or if the flag's value is the empty string.
|
||||
* @implNote This method does not delegate to {@link ErrorProneFlags#getSetOrEmpty(String)}, as
|
||||
* that method wouldn't allow us to identify a non-singleton set of empty strings; such a set
|
||||
* should not be treated as empty.
|
||||
*/
|
||||
public static ImmutableSet<String> getSet(ErrorProneFlags errorProneFlags, String name) {
|
||||
return ImmutableSet.copyOf(getList(errorProneFlags, name));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user