mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 15:49:33 +00:00
Compare commits
1 Commits
v0.17.0
...
maxsumrall
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56ed83bf49 |
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.3`).
|
||||
- Error Prone version (e.g. `2.15.0`).
|
||||
- Error Prone Support version (e.g. `0.3.0`).
|
||||
|
||||
### Additional context
|
||||
|
||||
|
||||
8
.github/release.yml
vendored
8
.github/release.yml
vendored
@@ -3,16 +3,16 @@ changelog:
|
||||
labels:
|
||||
- "ignore-changelog"
|
||||
categories:
|
||||
- title: ":warning: Update considerations and deprecations"
|
||||
labels:
|
||||
- "breaking change"
|
||||
- "deprecation"
|
||||
- title: ":rocket: New Error Prone checks and Refaster rules"
|
||||
labels:
|
||||
- "new feature"
|
||||
- title: ":sparkles: Improvements"
|
||||
labels:
|
||||
- "improvement"
|
||||
- title: ":warning: Update considerations and deprecations"
|
||||
labels:
|
||||
- "breaking change"
|
||||
- "deprecation"
|
||||
- title: ":bug: Bug fixes"
|
||||
labels:
|
||||
- "bug"
|
||||
|
||||
55
.github/workflows/build.yaml
vendored
Normal file
55
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Build and verify
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 11.0.16, 17.0.4, 19 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-12
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: ubuntu-22.04
|
||||
jdk: 20-ea
|
||||
distribution: zulu
|
||||
experimental: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
# We run the build twice for each supported JDK: once against the
|
||||
# original Error Prone release, using only Error Prone checks available
|
||||
# on Maven Central, and once against the Picnic Error Prone fork,
|
||||
# additionally enabling all checks defined in this project and any
|
||||
# Error Prone checks available only from other artifact repositories.
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.8.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
distribution: ${{ matrix.distribution }}
|
||||
cache: maven
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
- name: Build project against vanilla Error Prone, compile Javadoc
|
||||
run: mvn -T1C install javadoc:jar
|
||||
- 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
|
||||
|
||||
# XXX: Enable Codecov once we "go public".
|
||||
# XXX: Enable SonarCloud once we "go public".
|
||||
59
.github/workflows/build.yml
vendored
59
.github/workflows/build.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: Build and verify
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 17.0.10, 21.0.2 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-14
|
||||
jdk: 17.0.10
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.10
|
||||
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
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
github.com:443
|
||||
jitpack.io:443
|
||||
objects.githubusercontent.com:443
|
||||
repo.maven.apache.org:443
|
||||
# We run the build twice for each supported JDK: once against the
|
||||
# original Error Prone release, using only Error Prone checks available
|
||||
# on Maven Central, and once against the Picnic Error Prone fork,
|
||||
# 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
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
java-distribution: ${{ matrix.distribution }}
|
||||
maven-version: 3.9.6
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
- name: Build project against vanilla Error Prone, compile Javadoc
|
||||
run: mvn -T1C install javadoc:jar
|
||||
- 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
|
||||
|
||||
# XXX: Enable Codecov once we "go public".
|
||||
52
.github/workflows/codeql.yml
vendored
52
.github/workflows/codeql.yml
vendored
@@ -1,52 +0,0 @@
|
||||
# Analyzes the code using GitHub's default CodeQL query database.
|
||||
# Identified issues are registered with GitHub's code scanning dashboard. When
|
||||
# a pull request is analyzed, any offending lines are annotated. See
|
||||
# https://codeql.github.com for details.
|
||||
name: CodeQL analysis
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '0 4 * * 1'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
analyze:
|
||||
strategy:
|
||||
matrix:
|
||||
language: [ java, ruby ]
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
api.github.com:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
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
|
||||
with:
|
||||
java-version: 17.0.10
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Perform minimal build
|
||||
if: matrix.language == 'java'
|
||||
run: mvn -T1C clean package -DskipTests -Dverification.skip
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
49
.github/workflows/deploy-website.yaml
vendored
Normal file
49
.github/workflows/deploy-website.yaml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Update `error-prone.picnic.tech` website content
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master, website ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: ruby/setup-ruby@v1.126.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@v2.1.3
|
||||
- name: Generate documentation
|
||||
run: ./generate-docs.sh
|
||||
- name: Build website with Jekyll
|
||||
working-directory: ./website
|
||||
run: bundle exec jekyll build
|
||||
- name: Validate HTML output
|
||||
working-directory: ./website
|
||||
# XXX: Drop `--disable_external true` once we fully adopted the
|
||||
# "Refaster rules" terminology on our website and in the code.
|
||||
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
|
||||
- name: Upload website as artifact
|
||||
uses: actions/upload-pages-artifact@v1.0.5
|
||||
with:
|
||||
path: ./website/_site
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/website'
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1.2.3
|
||||
85
.github/workflows/deploy-website.yml
vendored
85
.github/workflows/deploy-website.yml
vendored
@@ -1,85 +0,0 @@
|
||||
name: Update `error-prone.picnic.tech` website content
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master, website ]
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
api.github.com:443
|
||||
bestpractices.coreinfrastructure.org:443
|
||||
blog.picnic.nl:443
|
||||
errorprone.info:443
|
||||
github.com:443
|
||||
img.shields.io:443
|
||||
index.rubygems.org:443
|
||||
jitpack.io:443
|
||||
maven.apache.org:443
|
||||
objects.githubusercontent.com:443
|
||||
pitest.org:443
|
||||
repo.maven.apache.org:443
|
||||
rubygems.org:443
|
||||
search.maven.org:443
|
||||
securityscorecards.dev:443
|
||||
sonarcloud.io:443
|
||||
www.baeldung.com:443
|
||||
www.bestpractices.dev:443
|
||||
www.youtube.com:443
|
||||
youtrack.jetbrains.com:443
|
||||
- name: Check out code
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
|
||||
- name: Generate documentation
|
||||
run: ./generate-docs.sh
|
||||
- name: Build website with Jekyll
|
||||
working-directory: ./website
|
||||
run: bundle exec jekyll build
|
||||
- name: Validate HTML output
|
||||
working-directory: ./website
|
||||
# XXX: Drop `--disable_external true` once we fully adopted the
|
||||
# "Refaster rules" terminology on our website and in the code.
|
||||
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
|
||||
- name: Upload website as artifact
|
||||
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
|
||||
with:
|
||||
path: ./website/_site
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/website'
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.github.com:443
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||
52
.github/workflows/openssf-scorecard.yml
vendored
52
.github/workflows/openssf-scorecard.yml
vendored
@@ -1,52 +0,0 @@
|
||||
# Analyzes the code base and GitHub project configuration for adherence to
|
||||
# security best practices for open source software. Identified issues are
|
||||
# registered with GitHub's code scanning dashboard. When a pull request is
|
||||
# analyzed, any offending lines are annotated. See
|
||||
# https://securityscorecards.dev for details.
|
||||
name: OpenSSF Scorecard update
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '0 4 * * 1'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
analyze:
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
id-token: write
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.github.com:443
|
||||
api.osv.dev:443
|
||||
api.scorecard.dev:443
|
||||
api.securityscorecards.dev:443
|
||||
fulcio.sigstore.dev:443
|
||||
github.com:443
|
||||
oss-fuzz-build-logs.storage.googleapis.com:443
|
||||
rekor.sigstore.dev:443
|
||||
tuf-repo-cdn.sigstore.dev:443
|
||||
www.bestpractices.dev:443
|
||||
- name: Check out code
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run OpenSSF Scorecard analysis
|
||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: ${{ github.ref == 'refs/heads/master' }}
|
||||
- name: Update GitHub's code scanning dashboard
|
||||
uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
25
.github/workflows/pitest-analyze-pr.yml
vendored
25
.github/workflows/pitest-analyze-pr.yml
vendored
@@ -11,23 +11,16 @@ jobs:
|
||||
analyze-pr:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
github.com:443
|
||||
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
|
||||
fetch-depth: 2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.8.0
|
||||
with:
|
||||
checkout-fetch-depth: 2
|
||||
java-version: 17.0.10
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
java-version: 17.0.4
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Run Pitest
|
||||
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
|
||||
# analyzes lines changed in the associated pull request, as GitHub
|
||||
@@ -38,7 +31,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@v3.1.1
|
||||
with:
|
||||
name: pitest-reports
|
||||
path: ./target/pit-reports-ci
|
||||
|
||||
33
.github/workflows/pitest-update-pr.yml
vendored
33
.github/workflows/pitest-update-pr.yml
vendored
@@ -9,35 +9,24 @@ on:
|
||||
- completed
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
jobs:
|
||||
update-pr:
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
permissions:
|
||||
actions: read
|
||||
checks: write
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
api.github.com:443
|
||||
github.com:443
|
||||
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
|
||||
with:
|
||||
java-version: 17.0.10
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
java-version: 17.0.4
|
||||
distribution: temurin
|
||||
cache: maven
|
||||
- name: Download Pitest analysis artifact
|
||||
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
|
||||
uses: dawidd6/action-download-artifact@v2.24.2
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
name: pitest-reports
|
||||
|
||||
53
.github/workflows/run-integration-tests.yml
vendored
53
.github/workflows/run-integration-tests.yml
vendored
@@ -1,53 +0,0 @@
|
||||
# If requested by means of a pull request comment, runs integration tests
|
||||
# against the project, using the code found on the pull request branch.
|
||||
# XXX: Generalize this to a matrix build of multiple integration tests,
|
||||
# possibly using multiple JDK or OS versions.
|
||||
# XXX: Investigate whether the comment can specify which integration tests run
|
||||
# run. See this example of a dynamic build matrix:
|
||||
# https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object
|
||||
name: "Integration tests"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [ created ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
run-integration-tests:
|
||||
name: On-demand integration test
|
||||
if: |
|
||||
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
checkstyle.org:443
|
||||
github.com:443
|
||||
objects.githubusercontent.com:443
|
||||
oss.sonatype.org:443
|
||||
raw.githubusercontent.com:443
|
||||
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
|
||||
with:
|
||||
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
|
||||
java-version: 17.0.10
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
- 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"
|
||||
- name: Upload artifacts on failure
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
with:
|
||||
name: integration-test-checkstyle
|
||||
path: "${{ runner.temp }}/artifacts"
|
||||
- name: Remove installed project artifacts
|
||||
run: mvn build-helper:remove-project-artifact
|
||||
50
.github/workflows/sonarcloud.yml
vendored
50
.github/workflows/sonarcloud.yml
vendored
@@ -1,50 +0,0 @@
|
||||
# Analyzes the code base using SonarCloud. See
|
||||
# https://sonarcloud.io/project/overview?id=PicnicSupermarket_error-prone-support.
|
||||
name: SonarCloud analysis
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '0 4 * * 1'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
analyze:
|
||||
# Analysis of code in forked repositories is skipped, as such workflow runs
|
||||
# do not have access to the requisite secrets.
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
allowed-endpoints: >
|
||||
api.adoptium.net:443
|
||||
api.sonarcloud.io: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
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
with:
|
||||
checkout-fetch-depth: 0
|
||||
java-version: 17.0.10
|
||||
java-distribution: temurin
|
||||
maven-version: 3.9.6
|
||||
- name: Create missing `test` directory
|
||||
# XXX: Drop this step in favour of actually having a test.
|
||||
run: mkdir refaster-compiler/src/test
|
||||
- name: Perform SonarCloud analysis
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: mvn -T1C jacoco:prepare-agent verify jacoco:report sonar:sonar -Dverification.skip -Dsonar.projectKey=PicnicSupermarket_error-prone-support
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -4,12 +4,8 @@
|
||||
.DS_Store
|
||||
.factorypath
|
||||
.idea
|
||||
!.idea/icon.svg
|
||||
.project
|
||||
.settings
|
||||
target
|
||||
*.iml
|
||||
*.swp
|
||||
|
||||
# The Git repositories checked out by the integration test framework.
|
||||
integration-tests/.repos
|
||||
|
||||
65
.idea/icon.svg
generated
65
.idea/icon.svg
generated
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1259 1199" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-624.154,-988.431)">
|
||||
<g transform="matrix(1,0,0,1,-70.1122,-35.0561)">
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1227.03,1988.79C1237.78,2070.45 1225.83,2190.1 1192.24,2194.53C1158.65,2198.95 1116.14,2086.46 1105.39,2004.81C1094.64,1923.16 1128.44,1902.11 1153.32,1898.84C1178.18,1895.56 1216.28,1907.14 1227.03,1988.79Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1151.08,1881.86C1134.93,1883.99 1114.77,1892.69 1101.6,1913.17C1088.42,1933.64 1082.71,1963.73 1088.42,2007.04C1094.04,2049.75 1107.59,2099.16 1124.51,2138.68C1132.97,2158.45 1142.15,2175.68 1152.59,2188.86C1163.04,2202.05 1176.31,2213.89 1194.48,2211.5C1212.65,2209.11 1222.39,2194.23 1229.07,2178.8C1235.75,2163.36 1240.15,2144.34 1243.21,2123.05C1249.32,2080.5 1249.63,2029.27 1244,1986.56C1238.3,1943.24 1225,1915.66 1206.98,1899.29C1188.95,1882.93 1167.22,1879.74 1151.08,1881.86ZM1155.55,1915.81C1164.27,1914.66 1174.03,1915.62 1183.96,1924.64C1193.89,1933.66 1205.01,1952.69 1210.06,1991.03C1215.18,2029.97 1214.89,2079.4 1209.32,2118.19C1206.53,2137.58 1202.32,2154.4 1197.65,2165.2C1194.14,2173.29 1190.82,2176.3 1189.96,2177.22C1188.89,2176.55 1184.91,2174.51 1179.43,2167.6C1172.12,2158.38 1163.7,2143.22 1155.99,2125.21C1140.57,2089.18 1127.49,2041.51 1122.36,2002.57C1117.32,1964.24 1123.13,1942.97 1130.39,1931.69C1137.65,1920.42 1146.82,1916.96 1155.55,1915.81Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1516.33,1963.1C1466.19,1897.75 1427.4,1906.77 1407.5,1922.05C1387.59,1937.32 1368.84,1972.45 1418.98,2037.8C1431.75,2054.44 1447.26,2071.84 1463.69,2088.19C1495.18,2119.52 1534.33,2139.39 1582.98,2126.14C1606.4,2119.76 1622.19,2110.46 1623.75,2098.64C1625.79,2083.16 1603,2065.78 1569.69,2050.47C1554.75,2019.83 1535.59,1988.2 1516.33,1963.1Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1397.07,1908.46C1409.99,1898.55 1430.41,1890.44 1454.2,1895.61C1478,1900.77 1503.31,1918 1529.91,1952.67C1548.92,1977.44 1567.3,2007.65 1582.28,2037.56C1597.47,2044.87 1610.74,2052.64 1621.09,2061.47C1632.68,2071.35 1642.93,2084.12 1640.73,2100.88C1639.05,2113.64 1630.31,2122.66 1620.9,2128.78C1611.49,2134.9 1600.29,2139.17 1587.48,2142.66C1532.39,2157.66 1485.57,2134.11 1451.61,2100.32C1434.7,2083.49 1418.73,2065.6 1405.39,2048.22C1378.79,2013.56 1368.69,1984.64 1369.86,1960.32C1371.04,1936 1384.15,1918.38 1397.07,1908.46ZM1417.92,1935.63C1410.94,1940.99 1404.71,1948.57 1404.07,1961.97C1403.43,1975.37 1409.02,1996.69 1432.56,2027.38C1444.76,2043.27 1459.82,2060.18 1475.77,2076.05C1504.8,2104.93 1536.26,2121.12 1578.48,2109.62C1589.1,2106.73 1597.5,2103.16 1602.23,2100.08C1605.14,2098.18 1606.16,2096.97 1606.54,2096.46C1606.07,2095.66 1604.57,2092.39 1598.86,2087.52C1591.24,2081.02 1578.31,2073.28 1562.54,2066.03L1556.98,2063.47L1554.29,2057.97C1539.86,2028.35 1521.12,1997.46 1502.75,1973.52C1479.2,1942.84 1460.05,1931.91 1446.94,1929.07C1433.84,1926.23 1424.9,1930.27 1417.92,1935.63Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M917.121,1633.14C845.801,1674.32 730.68,1709.07 713.738,1679.73C696.797,1650.39 784.453,1568.07 855.777,1526.89C927.102,1485.71 959.48,1508.89 972.02,1530.62C984.562,1552.34 988.445,1591.97 917.121,1633.14Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M986.848,1522.06C978.707,1507.95 962.949,1492.66 938.992,1488.32C915.031,1483.98 885.055,1490.22 847.219,1512.07C809.906,1533.61 769.453,1565.03 739.41,1595.79C724.391,1611.17 711.977,1626.24 703.797,1640.94C695.617,1655.64 689.746,1672.42 698.914,1688.29C708.078,1704.17 725.547,1707.48 742.363,1707.74C759.184,1708.01 778.445,1704.79 799.273,1699.47C840.934,1688.83 888.375,1669.51 925.684,1647.97C963.52,1626.12 983.91,1603.29 992.137,1580.37C1000.36,1557.45 994.988,1536.16 986.848,1522.06ZM957.195,1539.18C961.594,1546.79 964.438,1556.18 959.906,1568.8C955.379,1581.43 942.047,1598.98 908.562,1618.32C874.551,1637.96 828.77,1656.6 790.801,1666.3C771.816,1671.14 754.664,1673.69 742.902,1673.5C734.082,1673.37 730.035,1671.45 728.859,1671C729.062,1669.76 729.426,1665.3 733.715,1657.59C739.434,1647.31 750.215,1633.73 763.906,1619.72C791.285,1591.68 830.324,1561.36 864.336,1541.72C897.824,1522.38 919.695,1519.62 932.895,1522.01C946.09,1524.4 952.797,1531.56 957.195,1539.18Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1791.57,1526.89C1862.89,1568.07 1950.54,1650.39 1933.61,1679.74C1916.66,1709.08 1801.54,1674.33 1730.22,1633.15C1658.9,1591.97 1662.78,1552.34 1675.32,1530.62C1687.86,1508.89 1720.24,1485.72 1791.57,1526.89Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1660.5,1522.06C1652.35,1536.16 1646.98,1557.45 1655.21,1580.37C1663.43,1603.29 1683.82,1626.13 1721.66,1647.97C1758.97,1669.52 1806.41,1688.84 1848.07,1699.48C1868.9,1704.79 1888.16,1708.01 1904.98,1707.75C1921.79,1707.48 1939.27,1704.17 1948.43,1688.3C1957.59,1672.42 1951.73,1655.64 1943.55,1640.94C1935.37,1626.25 1922.95,1611.17 1907.93,1595.79C1877.89,1565.04 1837.43,1533.61 1800.12,1512.07C1762.29,1490.22 1732.31,1483.98 1708.35,1488.32C1684.39,1492.66 1668.64,1507.95 1660.5,1522.06ZM1690.15,1539.18C1694.55,1531.56 1701.25,1524.4 1714.45,1522.02C1727.64,1519.62 1749.52,1522.39 1783,1541.72C1817.02,1561.36 1856.06,1591.68 1883.44,1619.72C1897.12,1633.73 1907.91,1647.32 1913.63,1657.59C1917.92,1665.3 1918.28,1669.77 1918.48,1671.01C1917.31,1671.45 1913.26,1673.37 1904.44,1673.51C1892.68,1673.69 1875.52,1671.15 1856.54,1666.3C1818.57,1656.61 1772.79,1637.96 1738.78,1618.32C1705.29,1598.99 1691.97,1581.43 1687.43,1568.81C1682.91,1556.18 1685.75,1546.8 1690.15,1539.18Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1322.81,1013.67C1058.16,1014.17 843.293,1229.45 843.297,1494.11L843.223,1862.19C843.223,1955.32 919.055,2031.16 1012.19,2031.16C1054.55,2031.16 1093.39,2015.51 1123.09,1989.7C1169.54,2049.44 1242.17,2087.79 1323.7,2087.79C1405.22,2087.79 1477.84,2049.45 1524.28,1989.73C1553.98,2015.52 1592.81,2031.16 1635.15,2031.16C1728.29,2031.16 1804.12,1955.32 1804.12,1862.19L1804.12,1494.1C1804.12,1229.09 1588.7,1013.67 1323.69,1013.67L1322.84,1013.67L1322.81,1013.67ZM1322.92,1068.46L1323.69,1068.46C1559.09,1068.46 1749.33,1258.7 1749.33,1494.11L1749.33,1862.19C1749.33,1925.92 1698.88,1976.37 1635.15,1976.37C1596.91,1976.37 1563.67,1958.03 1542.94,1929.68L1517.91,1895.48L1497,1932.34C1462.85,1992.53 1398.48,2033 1323.7,2033C1248.9,2033 1184.52,1992.51 1150.38,1932.3L1129.45,1895.41L1104.43,1929.65C1083.69,1958.02 1050.44,1976.37 1012.19,1976.37C948.461,1976.37 898.016,1925.93 898.012,1862.2L898.086,1494.11C898.086,1259.03 1087.84,1068.92 1322.92,1068.47L1322.92,1068.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1322.86,1041.07C1072.99,1041.54 870.684,1244.23 870.688,1494.11L870.648,1862.19C870.648,1940.62 933.789,2003.77 1012.22,2003.77C1073.65,2003.77 1125.69,1965.03 1145.37,1910.56C1201.73,1934.69 1262.41,1947.14 1323.72,1947.14C1385.02,1947.14 1445.69,1934.69 1502.04,1910.57C1521.72,1965.04 1573.76,2003.77 1635.19,2003.77C1713.62,2003.77 1776.76,1940.62 1776.76,1862.19L1776.76,1494.11C1776.76,1243.9 1573.93,1041.07 1323.72,1041.07L1322.86,1041.07Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1323.7,1494.1C1449.2,1494.1 1550.24,1595.14 1550.24,1720.64L1550.24,1833.86C1550.24,1959.36 1449.2,2060.4 1323.7,2060.4C1198.2,2060.4 1097.16,1959.36 1097.16,1833.86L1097.16,1720.64C1097.16,1595.14 1198.2,1494.1 1323.7,1494.1Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1322.86,1041.07C1262.14,1041.18 1204.25,1053.27 1151.36,1075.05C1316.07,1142.87 1432.03,1304.93 1432.03,1494.11L1432.03,1811.95C1432.03,2003.63 1209.62,2024.85 1126.54,1945.82C1177.52,2034.1 1254.55,2060.4 1323.7,2060.4C1408.4,2060.4 1481.95,2014.37 1520.82,1945.86C1546.53,1981.01 1588.08,2003.77 1635.15,2003.77C1713.58,2003.77 1776.72,1940.62 1776.72,1862.19L1776.72,1494.11C1776.72,1243.9 1573.89,1041.07 1323.68,1041.07L1322.86,1041.07Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1322.86,1041.07C1304.33,1041.1 1286.06,1042.28 1268.11,1044.48C1492.04,1071.93 1665.46,1262.75 1665.46,1494.11L1665.46,1862.19C1665.46,1920.85 1630.14,1970.94 1579.54,1992.48C1596.59,1999.74 1615.38,2003.77 1635.15,2003.77C1713.58,2003.77 1776.72,1940.62 1776.72,1862.19L1776.72,1494.11C1776.72,1243.9 1573.89,1041.07 1323.68,1041.07L1322.86,1041.07Z" style="fill:rgb(189,191,175);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1322.85,1034.22C1069.29,1034.69 863.84,1240.54 863.84,1494.11L863.766,1862.19C863.766,1944.3 930.078,2010.61 1012.19,2010.61C1057.88,2010.61 1098.53,1989.94 1125.71,1957.58C1166.88,2023.5 1240.02,2067.25 1323.7,2067.25C1407.36,2067.25 1480.48,2023.52 1521.66,1957.62C1548.84,1989.96 1589.47,2010.61 1635.15,2010.61C1717.25,2010.61 1783.57,1944.3 1783.57,1862.19L1783.57,1494.11C1783.57,1240.2 1577.59,1034.21 1323.68,1034.22L1322.85,1034.22ZM1322.86,1047.92L1323.68,1047.92C1570.19,1047.92 1769.87,1247.6 1769.87,1494.11L1769.87,1862.19C1769.87,1936.95 1709.91,1996.92 1635.15,1996.92C1590.29,1996.92 1550.82,1975.26 1526.36,1941.82L1520.1,1933.27L1514.87,1942.48C1477.18,2008.91 1405.92,2053.55 1323.7,2053.55C1241.46,2053.55 1170.19,2008.89 1132.5,1942.44L1127.27,1933.21L1121.02,1941.77C1096.56,1975.24 1057.07,1996.92 1012.19,1996.92C937.43,1996.92 877.469,1936.95 877.465,1862.19L877.539,1494.11C877.539,1247.94 1076.7,1048.39 1322.86,1047.92Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1576.29,1470.72C1576.29,1481.36 1568.75,1491.56 1555.31,1499.08C1541.88,1506.6 1523.67,1510.83 1504.68,1510.83C1465.12,1510.83 1433.06,1492.87 1433.06,1470.72C1433.06,1448.57 1465.12,1430.62 1504.68,1430.62C1523.67,1430.62 1541.88,1434.84 1555.31,1442.36C1568.75,1449.89 1576.29,1460.09 1576.29,1470.72Z" style="fill:rgb(255,155,173);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1214.28,1470.72C1214.28,1481.36 1206.73,1491.56 1193.31,1499.08C1179.87,1506.6 1161.66,1510.83 1142.66,1510.83C1103.11,1510.83 1071.05,1492.87 1071.05,1470.72C1071.05,1448.57 1103.11,1430.62 1142.66,1430.62C1161.66,1430.62 1179.87,1434.84 1193.31,1442.36C1206.73,1449.89 1214.28,1460.09 1214.28,1470.72Z" style="fill:rgb(255,155,173);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1104.39,1401.46C1104.39,1375.15 1118.45,1350.79 1141.24,1337.63C1164.03,1324.48 1192.16,1324.48 1214.95,1337.63C1237.74,1350.79 1251.81,1375.15 1251.81,1401.46L1224.41,1401.46C1224.41,1384.9 1215.6,1369.64 1201.25,1361.36C1186.9,1353.07 1169.29,1353.07 1154.94,1361.36C1140.59,1369.64 1131.78,1384.9 1131.78,1401.46L1104.39,1401.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1395.54,1401.46C1395.54,1375.15 1409.61,1350.79 1432.39,1337.63C1455.18,1324.48 1483.32,1324.48 1506.11,1337.63C1528.89,1350.79 1542.96,1375.15 1542.96,1401.46L1515.56,1401.46C1515.56,1384.9 1506.75,1369.64 1492.41,1361.36C1478.06,1353.07 1460.44,1353.07 1446.09,1361.36C1431.74,1369.64 1422.93,1384.9 1422.93,1401.46L1395.54,1401.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(1,0,0,1,0,9.81959)">
|
||||
<path d="M1256.81,1448.61C1256.81,1472.48 1269.56,1494.57 1290.24,1506.51C1310.92,1518.45 1336.42,1518.45 1357.1,1506.51C1377.78,1494.57 1390.53,1472.48 1390.53,1448.61L1376.83,1448.61C1376.83,1467.61 1366.71,1485.15 1350.25,1494.65C1333.79,1504.15 1313.55,1504.15 1297.09,1494.65C1280.63,1485.15 1270.51,1467.61 1270.51,1448.61L1256.81,1448.61Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"helpers:pinGitHubActionDigests"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": [
|
||||
@@ -12,12 +9,10 @@
|
||||
"separateMinorPatch": true
|
||||
},
|
||||
{
|
||||
"matchPackageNames": [
|
||||
"dawidd6/action-download-artifact",
|
||||
"github/codeql-action",
|
||||
"ruby/setup-ruby"
|
||||
"matchPackagePatterns": [
|
||||
"^ruby\\/setup-ruby$"
|
||||
],
|
||||
"schedule": "every 4 weeks on Monday"
|
||||
"schedule": "* * 1 * *"
|
||||
}
|
||||
],
|
||||
"reviewers": [
|
||||
|
||||
@@ -48,17 +48,6 @@ be accepted. When in doubt, make sure to first raise an
|
||||
|
||||
To the extent possible, the pull request process guards our coding guidelines.
|
||||
Some pointers:
|
||||
- Try to make sure that the
|
||||
[`./run-full-build.sh`][error-prone-support-full-build] script completes
|
||||
successfully, ideally before opening a pull request. See the [development
|
||||
instructions][error-prone-support-developing] for details on how to
|
||||
efficiently resolve many of the errors and warnings that may be reported. (In
|
||||
particular, make sure to run `mvn fmt:format` and
|
||||
[`./apply-error-prone-suggestions.sh`][error-prone-support-patch].) That
|
||||
said, if you feel that the build fails for invalid or debatable reasons, or
|
||||
if you're unsure how to best resolve an issue, don't let that discourage you
|
||||
from opening a PR with a failing build; we can have a look at the issue
|
||||
together!
|
||||
- Checks should be _topical_: ideally they address a single concern.
|
||||
- Where possible checks should provide _fixes_, and ideally these are
|
||||
completely behavior-preserving. In order for a check to be adopted by users
|
||||
@@ -77,9 +66,6 @@ Some pointers:
|
||||
sneak in unrelated changes; instead just open more than one pull request 😉.
|
||||
|
||||
[error-prone-criteria]: https://errorprone.info/docs/criteria
|
||||
[error-prone-support-developing]: https://github.com/PicnicSupermarket/error-prone-support/tree/master#-developing-error-prone-support
|
||||
[error-prone-support-full-build]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-full-build.sh
|
||||
[error-prone-support-issues]: https://github.com/PicnicSupermarket/error-prone-support/issues
|
||||
[error-prone-support-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-branch-mutation-tests.sh
|
||||
[error-prone-support-patch]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/apply-error-prone-suggestions.sh
|
||||
[error-prone-support-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh
|
||||
[error-prone-support-pulls]: https://github.com/PicnicSupermarket/error-prone-support/pulls
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2024 Picnic Technologies BV
|
||||
Copyright (c) 2017-2023 Picnic Technologies BV
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
112
README.md
112
README.md
@@ -15,26 +15,13 @@ focussing on maintainability, consistency and avoidance of common pitfalls.
|
||||
> Error Prone is a static analysis tool for Java that catches common
|
||||
> programming mistakes at compile-time.
|
||||
|
||||
To learn more about Error Prone (Support), how you can start using Error Prone
|
||||
in practice, and how we use it at Picnic, watch the conference talk
|
||||
[_Automating away bugs with Error Prone in practice_][conference-talk]. Also
|
||||
consider checking out the blog post [_Picnic loves Error Prone: producing
|
||||
high-quality and consistent Java code_][picnic-blog-ep-post].
|
||||
Read more on how Picnic uses Error Prone (Support) in the blog post [_Picnic
|
||||
loves Error Prone: producing high-quality and consistent Java
|
||||
code_][picnic-blog-ep-post].
|
||||
|
||||
[![Maven Central][maven-central-badge]][maven-central-search]
|
||||
[![Reproducible Builds][reproducible-builds-badge]][reproducible-builds-report]
|
||||
[![OpenSSF Best Practices][openssf-best-practices-badge]][openssf-best-practices-checklist]
|
||||
[![OpenSSF Scorecard][openssf-scorecard-badge]][openssf-scorecard-report]
|
||||
[![CodeQL Analysis][codeql-badge]][codeql-master]
|
||||
[![GitHub Actions][github-actions-build-badge]][github-actions-build-master]
|
||||
[![Mutation tested with PIT][pitest-badge]][pitest]
|
||||
[![Quality Gate Status][sonarcloud-quality-badge]][sonarcloud-quality-master]
|
||||
[![Maintainability Rating][sonarcloud-maintainability-badge]][sonarcloud-maintainability-master]
|
||||
[![Reliability Rating][sonarcloud-reliability-badge]][sonarcloud-reliability-master]
|
||||
[![Security Rating][sonarcloud-security-badge]][sonarcloud-security-master]
|
||||
[![Coverage][sonarcloud-coverage-badge]][sonarcloud-coverage-master]
|
||||
[![Duplicated Lines (%)][sonarcloud-duplication-badge]][sonarcloud-duplication-master]
|
||||
[![Technical Debt][sonarcloud-technical-debt-badge]][sonarcloud-technical-debt-master]
|
||||
[![License][license-badge]][license]
|
||||
[![PRs Welcome][pr-badge]][contributing]
|
||||
|
||||
@@ -49,9 +36,7 @@ high-quality and consistent Java code_][picnic-blog-ep-post].
|
||||
### Installation
|
||||
|
||||
This library is built on top of [Error Prone][error-prone-orig-repo]. To use
|
||||
it, read the installation guide for Maven or Gradle below. The library requires
|
||||
that your build is executed using JDK 17 or above, but supports builds that
|
||||
[target][baeldung-java-source-target-options] older versions of Java.
|
||||
it, read the installation guide for Maven or Gradle below.
|
||||
|
||||
#### Maven
|
||||
|
||||
@@ -67,8 +52,6 @@ that your build is executed using JDK 17 or above, but supports builds that
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<!-- Prefer using the latest release. -->
|
||||
<version>3.12.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<!-- Error Prone itself. -->
|
||||
@@ -98,6 +81,8 @@ that your build is executed using JDK 17 or above, but supports builds that
|
||||
</arg>
|
||||
<arg>-XDcompilePolicy=simple</arg>
|
||||
</compilerArgs>
|
||||
<!-- Some checks raise warnings rather than errors. -->
|
||||
<showWarnings>true</showWarnings>
|
||||
<!-- Enable this if you'd like to fail your build upon warnings. -->
|
||||
<!-- <failOnWarning>true</failOnWarning> -->
|
||||
</configuration>
|
||||
@@ -113,9 +98,8 @@ definition. -->
|
||||
|
||||
#### Gradle
|
||||
|
||||
1. First, follow the [installation
|
||||
guide][error-prone-gradle-installation-guide] of the
|
||||
`gradle-errorprone-plugin`.
|
||||
1. First, follow the [installation guide]
|
||||
[error-prone-gradle-installation-guide] of the `gradle-errorprone-plugin`.
|
||||
2. Next, edit your `build.gradle` file to add one or more Error Prone Support
|
||||
modules:
|
||||
|
||||
@@ -187,15 +171,8 @@ rules][refaster-rules].
|
||||
## 👷 Developing Error Prone Support
|
||||
|
||||
This is a [Maven][maven] project, so running `mvn clean install` performs a
|
||||
full clean build and installs the library to your local Maven repository.
|
||||
|
||||
Once you've made changes, the build may fail due to a warning or error emitted
|
||||
by static code analysis. The flags and commands listed below allow you to
|
||||
suppress or (in a large subset of cases) automatically fix such cases. Make
|
||||
sure to carefully check the available options, as this can save you significant
|
||||
amounts of development time!
|
||||
|
||||
Relevant Maven build parameters:
|
||||
full clean build and installs the library to your local Maven repository. Some
|
||||
relevant flags:
|
||||
|
||||
- `-Dverification.warn` makes the warnings and errors emitted by various
|
||||
plugins and the Java compiler non-fatal, where possible.
|
||||
@@ -212,28 +189,19 @@ Relevant Maven build parameters:
|
||||
Pending a release of [google/error-prone#3301][error-prone-pull-3301], this
|
||||
flag must currently be used in combination with `-Perror-prone-fork`.
|
||||
|
||||
Other highly relevant commands:
|
||||
Some other commands one may find relevant:
|
||||
|
||||
- `mvn fmt:format` formats the code using
|
||||
[`google-java-format`][google-java-format].
|
||||
- [`./run-full-build.sh`][script-run-full-build] builds the project twice,
|
||||
where the second pass validates compatbility with Picnic's [Error Prone
|
||||
fork][error-prone-fork-repo] and compliance of the code with any rules
|
||||
defined within this project. (Consider running this before [opening a pull
|
||||
request][contributing-pull-request], as the PR checks also perform this
|
||||
validation.)
|
||||
- [`./apply-error-prone-suggestions.sh`][script-apply-error-prone-suggestions]
|
||||
applies Error Prone and Error Prone Support code suggestions to this project.
|
||||
Before running this command, make sure to have installed the project (`mvn
|
||||
clean install`) and make sure that the current working directory does not
|
||||
contain unstaged or uncommited changes.
|
||||
- [`./run-branch-mutation-tests.sh`][script-run-branch-mutation-tests] uses
|
||||
[Pitest][pitest] to run mutation tests against code that is modified relative
|
||||
to the upstream default branch. The results can be reviewed by opening the
|
||||
respective `target/pit-reports/index.html` files. One can use
|
||||
[`./run-mutation-tests.sh`][script-run-mutation-tests] to run mutation tests
|
||||
against _all_ code in the current working directory. For more information
|
||||
check the [PIT Maven plugin][pitest-maven].
|
||||
- `./run-mutation-tests.sh` runs mutation tests using [Pitest][pitest]. The
|
||||
results can be reviewed by opening the respective
|
||||
`target/pit-reports/index.html` files. For more information check the [PIT
|
||||
Maven plugin][pitest-maven].
|
||||
- `./apply-error-prone-suggestions.sh` applies Error Prone and Error Prone
|
||||
Support code suggestions to this project. Before running this command, make
|
||||
sure to have installed the project (`mvn clean install`) and make sure that
|
||||
the current working directory does not contain unstaged or uncommited
|
||||
changes.
|
||||
|
||||
When running the project's tests in IntelliJ IDEA, you might see the following
|
||||
error:
|
||||
@@ -260,19 +228,9 @@ Want to report or fix a bug, suggest or add a new feature, or improve the
|
||||
documentation? That's awesome! Please read our [contribution
|
||||
guidelines][contributing].
|
||||
|
||||
### Security
|
||||
|
||||
If you want to report a security vulnerability, please do so through a private
|
||||
channel; please see our [security policy][security] for details.
|
||||
|
||||
[baeldung-java-source-target-options]: https://www.baeldung.com/java-source-target-options
|
||||
[bug-checks]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/
|
||||
[bug-checks-identity-conversion]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java
|
||||
[codeql-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/codeql.yml/badge.svg?branch=master&event=push
|
||||
[codeql-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/codeql.yml?query=branch:master+event:push
|
||||
[conference-talk]: https://www.youtube.com/watch?v=-47WD-3wKBs
|
||||
[contributing]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md
|
||||
[contributing-pull-request]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md#-opening-a-pull-request
|
||||
[error-prone-bugchecker]: https://github.com/google/error-prone/blob/master/check_api/src/main/java/com/google/errorprone/bugpatterns/BugChecker.java
|
||||
[error-prone-fork-jitpack]: https://jitpack.io/#PicnicSupermarket/error-prone
|
||||
[error-prone-fork-repo]: https://github.com/PicnicSupermarket/error-prone
|
||||
@@ -280,8 +238,8 @@ channel; please see our [security policy][security] for details.
|
||||
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
|
||||
[error-prone-orig-repo]: https://github.com/google/error-prone
|
||||
[error-prone-pull-3301]: https://github.com/google/error-prone/pull/3301
|
||||
[github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yml/badge.svg
|
||||
[github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yml?query=branch:master&event=push
|
||||
[github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml/badge.svg
|
||||
[github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml?query=branch%3Amaster
|
||||
[google-java-format]: https://github.com/google/google-java-format
|
||||
[idea-288052]: https://youtrack.jetbrains.com/issue/IDEA-288052
|
||||
[license-badge]: https://img.shields.io/github/license/PicnicSupermarket/error-prone-support
|
||||
@@ -289,13 +247,8 @@ channel; please see our [security policy][security] for details.
|
||||
[maven-central-badge]: https://img.shields.io/maven-central/v/tech.picnic.error-prone-support/error-prone-support?color=blue
|
||||
[maven-central-search]: https://search.maven.org/artifact/tech.picnic.error-prone-support/error-prone-support
|
||||
[maven]: https://maven.apache.org
|
||||
[openssf-best-practices-badge]: https://bestpractices.coreinfrastructure.org/projects/7199/badge
|
||||
[openssf-best-practices-checklist]: https://bestpractices.coreinfrastructure.org/projects/7199
|
||||
[openssf-scorecard-badge]: https://img.shields.io/ossf-scorecard/github.com/PicnicSupermarket/error-prone-support?label=openssf%20scorecard
|
||||
[openssf-scorecard-report]: https://securityscorecards.dev/viewer/?uri=github.com/PicnicSupermarket/error-prone-support
|
||||
[picnic-blog-ep-post]: https://blog.picnic.nl/picnic-loves-error-prone-producing-high-quality-and-consistent-java-code-b8a566be6886
|
||||
[picnic-blog]: https://blog.picnic.nl
|
||||
[pitest-badge]: https://img.shields.io/badge/-Mutation%20tested%20with%20PIT-blue.svg
|
||||
[picnic-blog-ep-post]: https://blog.picnic.nl/picnic-loves-error-prone-producing-high-quality-and-consistent-java-code-b8a566be6886
|
||||
[pitest]: https://pitest.org
|
||||
[pitest-maven]: https://pitest.org/quickstart/maven
|
||||
[pr-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
|
||||
@@ -304,22 +257,3 @@ channel; please see our [security policy][security] for details.
|
||||
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
|
||||
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
|
||||
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
|
||||
[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
|
||||
[script-run-full-build]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-full-build.sh
|
||||
[script-run-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh
|
||||
[security]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/SECURITY.md
|
||||
[sonarcloud-coverage-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=coverage
|
||||
[sonarcloud-coverage-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=coverage
|
||||
[sonarcloud-duplication-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=duplicated_lines_density
|
||||
[sonarcloud-duplication-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=duplicated_lines_density
|
||||
[sonarcloud-maintainability-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=sqale_rating
|
||||
[sonarcloud-maintainability-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=sqale_rating
|
||||
[sonarcloud-quality-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=alert_status
|
||||
[sonarcloud-quality-master]: https://sonarcloud.io/summary/new_code?id=PicnicSupermarket_error-prone-support
|
||||
[sonarcloud-reliability-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=reliability_rating
|
||||
[sonarcloud-reliability-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=reliability_rating
|
||||
[sonarcloud-security-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=security_rating
|
||||
[sonarcloud-security-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=security_rating
|
||||
[sonarcloud-technical-debt-badge]: https://sonarcloud.io/api/project_badges/measure?project=PicnicSupermarket_error-prone-support&metric=sqale_index
|
||||
[sonarcloud-technical-debt-master]: https://sonarcloud.io/component_measures?id=PicnicSupermarket_error-prone-support&metric=sqale_index
|
||||
|
||||
23
SECURITY.md
23
SECURITY.md
@@ -1,23 +0,0 @@
|
||||
# Security policy
|
||||
|
||||
We take security seriously. We are mindful of Error Prone Support's place in
|
||||
the software supply chain, and the risks and responsibilities that come with
|
||||
this.
|
||||
|
||||
## Supported versions
|
||||
|
||||
This project uses [semantic versioning][semantic-versioning]. In general, only
|
||||
the latest version of this software is supported. That said, if users have a
|
||||
compelling reason to ask for patch release of an older major release, then we
|
||||
will seriously consider such a request. We do urge users to stay up-to-date and
|
||||
use the latest release where feasible.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
To report a vulnerability, please visit the [security
|
||||
advisories][security-advisories] page and click _Report a vulnerability_. We
|
||||
will take such reports seriously and work with you to resolve the issue in a
|
||||
timely manner.
|
||||
|
||||
[security-advisories]: https://github.com/PicnicSupermarket/error-prone-support/security/advisories
|
||||
[semantic-versioning]: https://semver.org
|
||||
@@ -1,7 +1,7 @@
|
||||
# Arcmutate license for Error Prone Support, requested by sending an email to
|
||||
# support@arcmutate.com.
|
||||
expires=27/10/2025
|
||||
expires=07/11/2023
|
||||
keyVersion=1
|
||||
signature=aQLVt0J//7nXDAlJuBe9ru7Dq6oApcLh17rxsgHoJqBkUCd2zvrtRxkcPm5PmF1I8gBDT9PHb+kTf6Kcfz+D1fT0wRrZv38ip56521AQPD6zjRSqak9O2Gisjo+QTPMD7oS009aSWKzQ1SMdtuZ+KIRvw4Gl+frtYzexqnGWEUQ\=
|
||||
signature=MhZxMbnO6UovNfllM0JuVWkZyvRT3/G5o/uT0Mm36c7200VpZNVu03gTAGivnl9W5RzvZhfpIHccuQ5ctjQkrqhsFSrl4fyqPqu3y5V2fsHIdFXP/G72EGj6Kay9ndLpaEHalqE0bEwxdnHMzEYq5y3O9vUPv8MhUl57xk+rvBo\=
|
||||
packages=tech.picnic.errorprone.*
|
||||
type=OSSS
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.8.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>documentation-support</artifactId>
|
||||
|
||||
<name>Picnic :: Error Prone Support :: Documentation Support</name>
|
||||
<description>Data extraction support for the purpose of documentation generation.</description>
|
||||
<url>https://error-prone.picnic.tech</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -41,14 +40,6 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-parameter-names</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
@@ -82,8 +73,6 @@
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- XXX: Explicitly declared as a workaround for
|
||||
https://github.com/pitest/pitest-junit5-plugin/issues/105. -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
|
||||
@@ -4,22 +4,18 @@ import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.auto.common.AnnotationMirrors;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.BugPattern.SeverityLevel;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.tools.javac.code.Attribute;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
|
||||
|
||||
@@ -27,40 +23,29 @@ import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocume
|
||||
* An {@link Extractor} that describes how to extract data from a {@code @BugPattern} annotation.
|
||||
*/
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
|
||||
/** Instantiates a new {@link BugPatternExtractor} instance. */
|
||||
public BugPatternExtractor() {}
|
||||
|
||||
final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "bugpattern";
|
||||
public BugPatternDocumentation extract(ClassTree tree, Context context) {
|
||||
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
|
||||
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
|
||||
requireNonNull(annotation, "BugPattern annotation must be present");
|
||||
|
||||
return new AutoValue_BugPatternExtractor_BugPatternDocumentation(
|
||||
symbol.getQualifiedName().toString(),
|
||||
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
|
||||
ImmutableList.copyOf(annotation.altNames()),
|
||||
annotation.link(),
|
||||
ImmutableList.copyOf(annotation.tags()),
|
||||
annotation.summary(),
|
||||
annotation.explanation(),
|
||||
annotation.severity(),
|
||||
annotation.disableable(),
|
||||
annotation.documentSuppression() ? getSuppressionAnnotations(tree) : ImmutableList.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BugPatternDocumentation> tryExtract(ClassTree tree, VisitorState state) {
|
||||
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
|
||||
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
|
||||
if (annotation == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
BugPatternDocumentation.create(
|
||||
state.getPath().getCompilationUnit().getSourceFile().toUri(),
|
||||
symbol.getQualifiedName().toString(),
|
||||
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
|
||||
ImmutableList.copyOf(annotation.altNames()),
|
||||
annotation.link(),
|
||||
ImmutableList.copyOf(annotation.tags()),
|
||||
annotation.summary(),
|
||||
annotation.explanation(),
|
||||
annotation.severity(),
|
||||
annotation.disableable(),
|
||||
annotation.documentSuppression()
|
||||
? getSuppressionAnnotations(tree)
|
||||
: ImmutableList.of()));
|
||||
public boolean canExtract(ClassTree tree) {
|
||||
return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,36 +79,7 @@ public final class BugPatternExtractor implements Extractor<BugPatternDocumentat
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_BugPatternExtractor_BugPatternDocumentation.class)
|
||||
abstract static class BugPatternDocumentation {
|
||||
static BugPatternDocumentation create(
|
||||
URI source,
|
||||
String fullyQualifiedName,
|
||||
String name,
|
||||
ImmutableList<String> altNames,
|
||||
String link,
|
||||
ImmutableList<String> tags,
|
||||
String summary,
|
||||
String explanation,
|
||||
SeverityLevel severityLevel,
|
||||
boolean canDisable,
|
||||
ImmutableList<String> suppressionAnnotations) {
|
||||
return new AutoValue_BugPatternExtractor_BugPatternDocumentation(
|
||||
source,
|
||||
fullyQualifiedName,
|
||||
name,
|
||||
altNames,
|
||||
link,
|
||||
tags,
|
||||
summary,
|
||||
explanation,
|
||||
severityLevel,
|
||||
canDisable,
|
||||
suppressionAnnotations);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String fullyQualifiedName();
|
||||
|
||||
abstract String name();
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static java.util.function.Predicate.not;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from classes that test a {@code
|
||||
* BugChecker}.
|
||||
*/
|
||||
// XXX: Handle other methods from `{BugCheckerRefactoring,Compilation}TestHelper`:
|
||||
// - Indicate which custom arguments are specified, if any.
|
||||
// - For replacement tests, indicate which `FixChooser` is used.
|
||||
// - ... (We don't use all optional features; TBD what else to support.)
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
|
||||
public BugPatternTestExtractor() {}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "bugpattern-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
BugPatternTestCollector collector = new BugPatternTestCollector();
|
||||
|
||||
collector.scan(tree, state);
|
||||
|
||||
return Optional.of(collector.getCollectedTests())
|
||||
.filter(not(ImmutableList::isEmpty))
|
||||
.map(
|
||||
tests ->
|
||||
new AutoValue_BugPatternTestExtractor_TestCases(
|
||||
state.getPath().getCompilationUnit().getSourceFile().toUri(),
|
||||
ASTHelpers.getSymbol(tree).className(),
|
||||
tests));
|
||||
}
|
||||
|
||||
private static final class BugPatternTestCollector
|
||||
extends TreeScanner<@Nullable Void, VisitorState> {
|
||||
private static final Matcher<ExpressionTree> COMPILATION_HELPER_DO_TEST =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
|
||||
.named("doTest");
|
||||
private static final Matcher<ExpressionTree> TEST_HELPER_NEW_INSTANCE =
|
||||
staticMethod()
|
||||
.onDescendantOfAny(
|
||||
"com.google.errorprone.CompilationTestHelper",
|
||||
"com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("newInstance")
|
||||
.withParameters(Class.class.getCanonicalName(), Class.class.getCanonicalName());
|
||||
private static final Matcher<ExpressionTree> IDENTIFICATION_SOURCE_LINES =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
|
||||
.named("addSourceLines");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_DO_TEST =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("doTest");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_EXPECT_UNCHANGED =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.named("expectUnchanged");
|
||||
private static final Matcher<ExpressionTree> REPLACEMENT_OUTPUT_SOURCE_LINES =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.namedAnyOf("addOutputLines", "expectUnchanged");
|
||||
|
||||
private final List<TestCase> collectedTestCases = new ArrayList<>();
|
||||
|
||||
private ImmutableList<TestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedTestCases);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) {
|
||||
boolean isReplacementTest = REPLACEMENT_DO_TEST.matches(node, state);
|
||||
if (isReplacementTest || COMPILATION_HELPER_DO_TEST.matches(node, state)) {
|
||||
getClassUnderTest(node, state)
|
||||
.ifPresent(
|
||||
classUnderTest -> {
|
||||
List<TestEntry> entries = new ArrayList<>();
|
||||
if (isReplacementTest) {
|
||||
extractReplacementTestCases(node, entries, state);
|
||||
} else {
|
||||
extractIdentificationTestCases(node, entries, state);
|
||||
}
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
collectedTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_TestCase(
|
||||
classUnderTest, ImmutableList.copyOf(entries).reverse()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.visitMethodInvocation(node, state);
|
||||
}
|
||||
|
||||
private static Optional<String> getClassUnderTest(
|
||||
MethodInvocationTree tree, VisitorState state) {
|
||||
if (TEST_HELPER_NEW_INSTANCE.matches(tree, state)) {
|
||||
return Optional.ofNullable(ASTHelpers.getSymbol(tree.getArguments().get(0)))
|
||||
.filter(s -> !s.type.allparams().isEmpty())
|
||||
.map(s -> s.type.allparams().get(0).tsym.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
return receiver instanceof MethodInvocationTree methodInvocation
|
||||
? getClassUnderTest(methodInvocation, state)
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private static void extractIdentificationTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) {
|
||||
String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
|
||||
Optional<String> sourceCode =
|
||||
getSourceCode(tree).filter(s -> s.contains("// BUG: Diagnostic"));
|
||||
if (path != null && sourceCode.isPresent()) {
|
||||
sink.add(
|
||||
new AutoValue_BugPatternTestExtractor_IdentificationTestEntry(
|
||||
path, sourceCode.orElseThrow()));
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractIdentificationTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractReplacementTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) {
|
||||
/*
|
||||
* Retrieve the method invocation that contains the input source code. Note that this cast
|
||||
* is safe, because this code is guarded by an earlier call to `#getClassUnderTest(..)`,
|
||||
* which ensures that `tree` is part of a longer method invocation chain.
|
||||
*/
|
||||
MethodInvocationTree inputTree = (MethodInvocationTree) ASTHelpers.getReceiver(tree);
|
||||
|
||||
String path = ASTHelpers.constValue(inputTree.getArguments().get(0), String.class);
|
||||
Optional<String> inputCode = getSourceCode(inputTree);
|
||||
if (path != null && inputCode.isPresent()) {
|
||||
Optional<String> outputCode =
|
||||
REPLACEMENT_EXPECT_UNCHANGED.matches(tree, state) ? inputCode : getSourceCode(tree);
|
||||
|
||||
if (outputCode.isPresent() && !inputCode.equals(outputCode)) {
|
||||
sink.add(
|
||||
new AutoValue_BugPatternTestExtractor_ReplacementTestEntry(
|
||||
path, inputCode.orElseThrow(), outputCode.orElseThrow()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractReplacementTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: This logic is duplicated in `ErrorProneTestSourceFormat`. Can we do better?
|
||||
private static Optional<String> getSourceCode(MethodInvocationTree tree) {
|
||||
List<? extends ExpressionTree> sourceLines =
|
||||
tree.getArguments().subList(1, tree.getArguments().size());
|
||||
StringBuilder source = new StringBuilder();
|
||||
|
||||
for (ExpressionTree sourceLine : sourceLines) {
|
||||
String value = ASTHelpers.constValue(sourceLine, String.class);
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
source.append(value).append('\n');
|
||||
}
|
||||
|
||||
return Optional.of(source.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@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);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String testClass();
|
||||
|
||||
abstract ImmutableList<TestCase> 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);
|
||||
}
|
||||
|
||||
abstract String classUnderTest();
|
||||
|
||||
abstract ImmutableList<TestEntry> entries();
|
||||
}
|
||||
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(AutoValue_BugPatternTestExtractor_IdentificationTestEntry.class),
|
||||
@JsonSubTypes.Type(AutoValue_BugPatternTestExtractor_ReplacementTestEntry.class)
|
||||
})
|
||||
@JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "type", use = JsonTypeInfo.Id.DEDUCTION)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@JsonPropertyOrder("type")
|
||||
interface TestEntry {
|
||||
TestType type();
|
||||
|
||||
String path();
|
||||
|
||||
enum TestType {
|
||||
IDENTIFICATION,
|
||||
REPLACEMENT
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class IdentificationTestEntry implements TestEntry {
|
||||
static IdentificationTestEntry create(String path, String code) {
|
||||
return new AutoValue_BugPatternTestExtractor_IdentificationTestEntry(path, code);
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@Override
|
||||
public final TestType type() {
|
||||
return TestType.IDENTIFICATION;
|
||||
}
|
||||
|
||||
abstract String code();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class ReplacementTestEntry implements TestEntry {
|
||||
static ReplacementTestEntry create(String path, String input, String output) {
|
||||
return new AutoValue_BugPatternTestExtractor_ReplacementTestEntry(path, input, output);
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
@Override
|
||||
public final TestType type() {
|
||||
return TestType.REPLACEMENT;
|
||||
}
|
||||
|
||||
abstract String input();
|
||||
|
||||
abstract String output();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskEvent.Kind;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.api.JavacTrees;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -24,12 +27,8 @@ import javax.tools.JavaFileObject;
|
||||
*/
|
||||
// XXX: Find a better name for this class; it doesn't generate documentation per se.
|
||||
final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static final ImmutableList<Extractor<?>> EXTRACTORS =
|
||||
(ImmutableList)
|
||||
ImmutableList.copyOf(
|
||||
ServiceLoader.load(
|
||||
Extractor.class, DocumentationGeneratorTaskListener.class.getClassLoader()));
|
||||
private static final ObjectMapper OBJECT_MAPPER =
|
||||
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
|
||||
|
||||
private final Context context;
|
||||
private final Path docsPath;
|
||||
@@ -52,25 +51,19 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
return;
|
||||
}
|
||||
|
||||
JavaFileObject sourceFile = taskEvent.getSourceFile();
|
||||
CompilationUnitTree compilationUnit = taskEvent.getCompilationUnit();
|
||||
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
|
||||
if (sourceFile == null || compilationUnit == null || classTree == null) {
|
||||
JavaFileObject sourceFile = taskEvent.getSourceFile();
|
||||
if (classTree == null || sourceFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
VisitorState state =
|
||||
VisitorState.createForUtilityPurposes(context)
|
||||
.withPath(new TreePath(new TreePath(compilationUnit), classTree));
|
||||
|
||||
for (Extractor<?> extractor : EXTRACTORS) {
|
||||
extractor
|
||||
.tryExtract(classTree, state)
|
||||
.ifPresent(
|
||||
data ->
|
||||
writeToFile(
|
||||
extractor.identifier(), getSimpleClassName(sourceFile.toUri()), data));
|
||||
}
|
||||
ExtractorType.findMatchingType(classTree)
|
||||
.ifPresent(
|
||||
extractorType ->
|
||||
writeToFile(
|
||||
extractorType.getIdentifier(),
|
||||
getSimpleClassName(sourceFile.toUri()),
|
||||
extractorType.getExtractor().extract(classTree, context)));
|
||||
}
|
||||
|
||||
private void createDocsDirectory() {
|
||||
@@ -83,7 +76,13 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
|
||||
}
|
||||
|
||||
private <T> void writeToFile(String identifier, String className, T data) {
|
||||
Json.write(docsPath.resolve(String.format("%s-%s.json", identifier, className)), data);
|
||||
File file = docsPath.resolve(String.format("%s-%s.json", identifier, className)).toFile();
|
||||
|
||||
try (FileWriter fileWriter = new FileWriter(file, UTF_8)) {
|
||||
OBJECT_MAPPER.writeValue(fileWriter, data);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(String.format("Cannot write to file '%s'", file.getPath()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSimpleClassName(URI path) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import java.util.Optional;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that define how to extract data of some type {@link T} from a
|
||||
@@ -14,20 +13,21 @@ import java.util.Optional;
|
||||
@Immutable
|
||||
interface Extractor<T> {
|
||||
/**
|
||||
* Returns the unique identifier of this extractor.
|
||||
* Extracts and returns an instance of {@link T} using the provided arguments.
|
||||
*
|
||||
* @return A non-{@code null} string.
|
||||
* @param tree The {@link ClassTree} to analyze and from which to extract instances of {@link T}.
|
||||
* @param context The {@link Context} in which the current compilation takes place.
|
||||
* @return A non-null instance of {@link T}.
|
||||
*/
|
||||
String identifier();
|
||||
// XXX: Drop `Context` parameter unless used.
|
||||
T extract(ClassTree tree, Context context);
|
||||
|
||||
/**
|
||||
* Attempts to extract an instance of type {@link T} using the provided arguments.
|
||||
* Tells whether this {@link Extractor} can extract documentation content from the given {@link
|
||||
* ClassTree}.
|
||||
*
|
||||
* @param tree The {@link ClassTree} to analyze and from which to extract an instance of type
|
||||
* {@link T}.
|
||||
* @param state A {@link VisitorState} describing the context in which the given {@link ClassTree}
|
||||
* is found.
|
||||
* @return An instance of type {@link T}, if possible.
|
||||
* @param tree The {@link ClassTree} of interest.
|
||||
* @return {@code true} iff data extraction is supported.
|
||||
*/
|
||||
Optional<T> tryExtract(ClassTree tree, VisitorState state);
|
||||
boolean canExtract(ClassTree tree);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Optional;
|
||||
|
||||
/** An enumeration of {@link Extractor} types. */
|
||||
enum ExtractorType {
|
||||
BUG_PATTERN("bugpattern", new BugPatternExtractor());
|
||||
|
||||
private static final ImmutableSet<ExtractorType> TYPES =
|
||||
Sets.immutableEnumSet(EnumSet.allOf(ExtractorType.class));
|
||||
|
||||
private final String identifier;
|
||||
private final Extractor<?> extractor;
|
||||
|
||||
ExtractorType(String identifier, Extractor<?> extractor) {
|
||||
this.identifier = identifier;
|
||||
this.extractor = extractor;
|
||||
}
|
||||
|
||||
String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
Extractor<?> getExtractor() {
|
||||
return extractor;
|
||||
}
|
||||
|
||||
static Optional<ExtractorType> findMatchingType(ClassTree tree) {
|
||||
return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree)).findFirst();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.guava.GuavaModule;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
import com.google.errorprone.annotations.FormatMethod;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Utility class that offers mutually consistent JSON serialization and deserialization operations,
|
||||
* without further specifying the exact schema used.
|
||||
*/
|
||||
final class Json {
|
||||
private static final ObjectMapper OBJECT_MAPPER =
|
||||
new ObjectMapper()
|
||||
.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
|
||||
.registerModules(new GuavaModule(), new ParameterNamesModule());
|
||||
|
||||
private Json() {}
|
||||
|
||||
static <T> T read(Path path, Class<T> clazz) {
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(path.toFile(), clazz);
|
||||
} catch (IOException e) {
|
||||
throw failure(e, "Failure reading from '%s'", path);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> void write(Path path, T object) {
|
||||
try {
|
||||
OBJECT_MAPPER.writeValue(path.toFile(), object);
|
||||
} catch (IOException e) {
|
||||
throw failure(e, "Failure writing to '%s'", path);
|
||||
}
|
||||
}
|
||||
|
||||
@FormatMethod
|
||||
private static UncheckedIOException failure(IOException cause, String format, Object... args) {
|
||||
return new UncheckedIOException(String.format(format, args), cause);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import java.net.URI;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
|
||||
|
||||
final class BugPatternExtractorTest {
|
||||
@Test
|
||||
@@ -27,7 +33,7 @@ final class BugPatternExtractorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void minimalBugPattern(@TempDir Path outputDirectory) {
|
||||
void minimalBugPattern(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"MinimalBugChecker.java",
|
||||
@@ -40,25 +46,14 @@ final class BugPatternExtractorTest {
|
||||
"@BugPattern(summary = \"MinimalBugChecker summary\", severity = SeverityLevel.ERROR)",
|
||||
"public final class MinimalBugChecker extends BugChecker {}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"MinimalBugChecker",
|
||||
BugPatternDocumentation.create(
|
||||
URI.create("file:///MinimalBugChecker.java"),
|
||||
"pkg.MinimalBugChecker",
|
||||
"MinimalBugChecker",
|
||||
ImmutableList.of(),
|
||||
"",
|
||||
ImmutableList.of(),
|
||||
"MinimalBugChecker summary",
|
||||
"",
|
||||
ERROR,
|
||||
/* canDisable= */ true,
|
||||
ImmutableList.of(SuppressWarnings.class.getCanonicalName())));
|
||||
"bugpattern-MinimalBugChecker.json",
|
||||
"bugpattern-documentation-minimal.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
void completeBugPattern(@TempDir Path outputDirectory) {
|
||||
void completeBugPattern(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"CompleteBugChecker.java",
|
||||
@@ -82,25 +77,14 @@ final class BugPatternExtractorTest {
|
||||
" suppressionAnnotations = {BugPattern.class, Test.class})",
|
||||
"public final class CompleteBugChecker extends BugChecker {}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"CompleteBugChecker",
|
||||
BugPatternDocumentation.create(
|
||||
URI.create("file:///CompleteBugChecker.java"),
|
||||
"pkg.CompleteBugChecker",
|
||||
"OtherName",
|
||||
ImmutableList.of("Check"),
|
||||
"https://error-prone.picnic.tech",
|
||||
ImmutableList.of("Simplification"),
|
||||
"CompleteBugChecker summary",
|
||||
"Example explanation",
|
||||
SUGGESTION,
|
||||
/* canDisable= */ false,
|
||||
ImmutableList.of(BugPattern.class.getCanonicalName(), "org.junit.jupiter.api.Test")));
|
||||
"bugpattern-CompleteBugChecker.json",
|
||||
"bugpattern-documentation-complete.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
void undocumentedSuppressionBugPattern(@TempDir Path outputDirectory) {
|
||||
void undocumentedSuppressionBugPattern(@TempDir Path outputDirectory) throws IOException {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"UndocumentedSuppressionBugPattern.java",
|
||||
@@ -116,27 +100,54 @@ final class BugPatternExtractorTest {
|
||||
" documentSuppression = false)",
|
||||
"public final class UndocumentedSuppressionBugPattern extends BugChecker {}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
verifyFileMatchesResource(
|
||||
outputDirectory,
|
||||
"UndocumentedSuppressionBugPattern",
|
||||
BugPatternDocumentation.create(
|
||||
URI.create("file:///UndocumentedSuppressionBugPattern.java"),
|
||||
"pkg.UndocumentedSuppressionBugPattern",
|
||||
"UndocumentedSuppressionBugPattern",
|
||||
ImmutableList.of(),
|
||||
"",
|
||||
ImmutableList.of(),
|
||||
"UndocumentedSuppressionBugPattern summary",
|
||||
"",
|
||||
WARNING,
|
||||
/* canDisable= */ true,
|
||||
ImmutableList.of()));
|
||||
"bugpattern-UndocumentedSuppressionBugPattern.json",
|
||||
"bugpattern-documentation-undocumented-suppression.json");
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(
|
||||
Path outputDirectory, String testClass, BugPatternDocumentation expected) {
|
||||
assertThat(outputDirectory.resolve(String.format("bugpattern-%s.json", testClass)))
|
||||
.exists()
|
||||
.returns(expected, path -> Json.read(path, BugPatternDocumentation.class));
|
||||
@Test
|
||||
void bugPatternAnnotationIsAbsent() {
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"TestChecker.java",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: Can extract: false",
|
||||
"public final class TestChecker extends BugChecker {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
private static void verifyFileMatchesResource(
|
||||
Path outputDirectory, String fileName, String resourceName) throws IOException {
|
||||
assertThat(Files.readString(outputDirectory.resolve(fileName)))
|
||||
.isEqualToIgnoringWhitespace(getResource(resourceName));
|
||||
}
|
||||
|
||||
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
|
||||
// text blocks in this class. (This also requires renaming the `verifyFileMatchesResource`
|
||||
// method.)
|
||||
private static String getResource(String resourceName) throws IOException {
|
||||
return Resources.toString(
|
||||
Resources.getResource(BugPatternExtractorTest.class, resourceName), UTF_8);
|
||||
}
|
||||
|
||||
/** A {@link BugChecker} that validates the {@link BugPatternExtractor}. */
|
||||
@BugPattern(summary = "Validates `BugPatternExtractor` extraction", severity = ERROR)
|
||||
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
BugPatternExtractor extractor = new BugPatternExtractor();
|
||||
|
||||
assertThatThrownBy(() -> extractor.extract(tree, state.context))
|
||||
.isInstanceOf(NullPointerException.class)
|
||||
.hasMessage("BugPattern annotation must be present");
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage(String.format("Can extract: %s", extractor.canExtract(tree)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,558 +0,0 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
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.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
|
||||
void noTestClass(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerWithoutAnnotation.java",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"public final class TestCheckerWithoutAnnotation extends BugChecker {}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noDoTestInvocation(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\");",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\");",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullBugCheckerInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance((Class<BugChecker>) null, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance((Class<BugChecker>) null, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void rawBugCheckerInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" @SuppressWarnings(\"unchecked\")",
|
||||
" void m() {",
|
||||
" @SuppressWarnings(\"rawtypes\")",
|
||||
" Class bugChecker = TestChecker.class;",
|
||||
"",
|
||||
" CompilationTestHelper.newInstance(bugChecker, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(bugChecker, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scannerSupplierInstance(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"import com.google.errorprone.scanner.ScannerSupplier;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(",
|
||||
" ScannerSupplier.fromBugCheckerClasses(TestChecker.class), getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(",
|
||||
" ScannerSupplier.fromBugCheckerClasses(TestChecker.class), getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonCompileTimeConstantStrings(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(toString() + \"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .addSourceLines(\"B.java\", \"// BUG: Diagnostic contains:\", \"class B {}\", toString())",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(toString() + \"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\", toString())",
|
||||
" .addOutputLines(\"B.java\", \"class B { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"C.java\", \"class C {}\")",
|
||||
" .addOutputLines(\"C.java\", \"class C { /* This is a change. */ }\", toString())",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonFluentTestHelperExpressions(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper testHelper =",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"class A {}\");",
|
||||
" testHelper.doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.ExpectOutput expectedOutput =",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\");",
|
||||
" expectedOutput.addOutputLines(\"A.java\", \"class A {}\").doTest();",
|
||||
" expectedOutput.expectUnchanged().doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noSource(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass()).doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass()).doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void noDiagnostics(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class TestCheckerTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A {}\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\")",
|
||||
" .expectUnchanged()",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileCompilationTestHelper(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileCompilationTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///SingleFileCompilationTestHelperTest.java"),
|
||||
"SingleFileCompilationTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"SingleFileCompilationTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileCompilationTestHelperWithSetArgs(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileCompilationTestHelperWithSetArgsTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .setArgs(\"-XepAllSuggestionsAsWarnings\")",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///SingleFileCompilationTestHelperWithSetArgsTest.java"),
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"SingleFileCompilationTestHelperWithSetArgsTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiFileCompilationTestHelper(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"MultiFileCompilationTestHelperTest.java",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class MultiFileCompilationTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .addSourceLines(\"B.java\", \"// BUG: Diagnostic contains:\", \"class B {}\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"MultiFileCompilationTestHelperTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///MultiFileCompilationTestHelperTest.java"),
|
||||
"MultiFileCompilationTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"MultiFileCompilationTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"),
|
||||
IdentificationTestEntry.create(
|
||||
"B.java", "// BUG: Diagnostic contains:\nclass B {}\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileBugCheckerRefactoringTestHelper(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileBugCheckerRefactoringTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///SingleFileBugCheckerRefactoringTestHelperTest.java"),
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"SingleFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
"A.java", "class A {}\n", "class A { /* This is a change. */ }\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestMode(
|
||||
@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .setArgs(\"-XepAllSuggestionsAsWarnings\")",
|
||||
" .setFixChooser(FixChoosers.SECOND)",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest(TestMode.TEXT_MATCH);",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
|
||||
TestCases.create(
|
||||
URI.create(
|
||||
"file:///SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java"),
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
"A.java", "class A {}\n", "class A { /* This is a change. */ }\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiFileBugCheckerRefactoringTestHelper(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class MultiFileBugCheckerRefactoringTestHelperTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .addInputLines(\"B.java\", \"class B {}\")",
|
||||
" .addOutputLines(\"B.java\", \"class B { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///MultiFileBugCheckerRefactoringTestHelperTest.java"),
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"MultiFileBugCheckerRefactoringTestHelperTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
"A.java", "class A {}\n", "class A { /* This is a change. */ }\n"),
|
||||
ReplacementTestEntry.create(
|
||||
"B.java", "class B {}\n", "class B { /* This is a change. */ }\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compilationAndBugCheckerRefactoringTestHelpers(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.java",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class CompilationAndBugCheckerRefactoringTestHelpersTest {",
|
||||
" private static class TestChecker extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest",
|
||||
TestCases.create(
|
||||
URI.create("file:///CompilationAndBugCheckerRefactoringTestHelpersTest.java"),
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
|
||||
TestCase.create(
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
"A.java", "class A {}\n", "class A { /* This is a change. */ }\n"))))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNames(
|
||||
@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
|
||||
"import com.google.errorprone.CompilationTestHelper;",
|
||||
"import com.google.errorprone.bugpatterns.BugChecker;",
|
||||
"",
|
||||
"final class CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest {",
|
||||
" private static class CustomTestChecker extends BugChecker {}",
|
||||
"",
|
||||
" private static class CustomTestChecker2 extends BugChecker {}",
|
||||
"",
|
||||
" void m() {",
|
||||
" CompilationTestHelper.newInstance(CustomTestChecker.class, getClass())",
|
||||
" .addSourceLines(\"A.java\", \"// BUG: Diagnostic contains:\", \"class A {}\")",
|
||||
" .doTest();",
|
||||
"",
|
||||
" BugCheckerRefactoringTestHelper.newInstance(CustomTestChecker2.class, getClass())",
|
||||
" .addInputLines(\"A.java\", \"class A {}\")",
|
||||
" .addOutputLines(\"A.java\", \"class A { /* This is a change. */ }\")",
|
||||
" .doTest();",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
|
||||
TestCases.create(
|
||||
URI.create(
|
||||
"file:///CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java"),
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
|
||||
ImmutableList.of(
|
||||
TestCase.create(
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker",
|
||||
ImmutableList.of(
|
||||
IdentificationTestEntry.create(
|
||||
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
|
||||
TestCase.create(
|
||||
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker2",
|
||||
ImmutableList.of(
|
||||
ReplacementTestEntry.create(
|
||||
"A.java", "class A {}\n", "class A { /* This is a change. */ }\n"))))));
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(
|
||||
Path outputDirectory, String testClass, TestCases expected) {
|
||||
assertThat(outputDirectory.resolve(String.format("bugpattern-test-%s.json", testClass)))
|
||||
.exists()
|
||||
.returns(expected, path -> Json.read(path, TestCases.class));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.FileManagers;
|
||||
import com.google.errorprone.FileObjects;
|
||||
@@ -9,66 +7,39 @@ import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
// XXX: Generalize and move this class so that it can also be used by `refaster-compiler`.
|
||||
// XXX: This class is supported by the `ErrorProneTestHelperSourceFormat` check, but until that
|
||||
// support is covered by unit tests, make sure to update that logic if this class or its methods are
|
||||
// moved/renamed.
|
||||
// XXX: Add support for this class to the `ErrorProneTestHelperSourceFormat` check.
|
||||
public final class Compilation {
|
||||
private Compilation() {}
|
||||
|
||||
public static void compileWithDocumentationGenerator(
|
||||
Path outputDirectory, String path, String... lines) {
|
||||
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), path, lines);
|
||||
Path outputDirectory, String fileName, String... lines) {
|
||||
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), fileName, lines);
|
||||
}
|
||||
|
||||
public static void compileWithDocumentationGenerator(
|
||||
String outputDirectory, String path, String... lines) {
|
||||
/*
|
||||
* The compiler options specified here largely match those used by Error Prone's
|
||||
* `CompilationTestHelper`. A key difference is the stricter linting configuration. When
|
||||
* compiling using JDK 21+, these lint options also require that certain JDK modules are
|
||||
* explicitly exported.
|
||||
*/
|
||||
String outputDirectory, String fileName, String... lines) {
|
||||
compile(
|
||||
ImmutableList.of(
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"-encoding",
|
||||
"UTF-8",
|
||||
"-parameters",
|
||||
"-proc:none",
|
||||
"-Werror",
|
||||
"-Xlint:all,-serial",
|
||||
"-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory,
|
||||
"-XDdev",
|
||||
"-XDcompilePolicy=simple"),
|
||||
FileObjects.forSourceLines(path, lines));
|
||||
ImmutableList.of("-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory),
|
||||
FileObjects.forSourceLines(fileName, lines));
|
||||
}
|
||||
|
||||
private static void compile(ImmutableList<String> options, JavaFileObject javaFileObject) {
|
||||
JavacFileManager javacFileManager = FileManagers.testFileManager();
|
||||
JavaCompiler compiler = JavacTool.create();
|
||||
|
||||
List<Diagnostic<?>> diagnostics = new ArrayList<>();
|
||||
JavacTaskImpl task =
|
||||
(JavacTaskImpl)
|
||||
compiler.getTask(
|
||||
null,
|
||||
javacFileManager,
|
||||
diagnostics::add,
|
||||
null,
|
||||
options,
|
||||
ImmutableList.of(),
|
||||
ImmutableList.of(javaFileObject));
|
||||
|
||||
Boolean result = task.call();
|
||||
assertThat(diagnostics).isEmpty();
|
||||
assertThat(result).isTrue();
|
||||
task.call();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.file.attribute.AclEntryPermission.ADD_SUBDIRECTORY;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.condition.OS.WINDOWS;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.AclEntry;
|
||||
import java.nio.file.attribute.AclFileAttributeView;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
@@ -77,71 +67,11 @@ final class DocumentationGeneratorTaskListenerTest {
|
||||
|
||||
@Test
|
||||
void excessArguments(@TempDir Path outputDirectory) {
|
||||
String actualOutputDirectory = outputDirectory.toAbsolutePath() + " extra-arg";
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
actualOutputDirectory, "A.java", "package pkg;"))
|
||||
outputDirectory.toAbsolutePath() + " extra-arg", "A.java", "package pkg;"))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Precisely one path must be provided");
|
||||
}
|
||||
|
||||
@Test
|
||||
void extraction(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"DocumentationGeneratorTaskListenerTestClass.java",
|
||||
"class DocumentationGeneratorTaskListenerTestClass {}");
|
||||
|
||||
assertThat(
|
||||
outputDirectory.resolve(
|
||||
"documentation-generator-task-listener-test-DocumentationGeneratorTaskListenerTestClass.json"))
|
||||
.content(UTF_8)
|
||||
.isEqualToIgnoringWhitespace(
|
||||
"""
|
||||
{
|
||||
"className": "DocumentationGeneratorTaskListenerTestClass",
|
||||
"path": [
|
||||
"CLASS: DocumentationGeneratorTaskListenerTestClass",
|
||||
"COMPILATION_UNIT"
|
||||
]
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public static final class TestExtractor implements Extractor<ExtractionParameters> {
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "documentation-generator-task-listener-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ExtractionParameters> tryExtract(ClassTree tree, VisitorState state) {
|
||||
return Optional.of(tree.getSimpleName().toString())
|
||||
.filter(n -> n.contains(DocumentationGeneratorTaskListenerTest.class.getSimpleName()))
|
||||
.map(
|
||||
className ->
|
||||
new AutoValue_DocumentationGeneratorTaskListenerTest_ExtractionParameters(
|
||||
className,
|
||||
Streams.stream(state.getPath())
|
||||
.map(TestExtractor::describeTree)
|
||||
.collect(toImmutableList())));
|
||||
}
|
||||
|
||||
private static String describeTree(Tree tree) {
|
||||
return (tree instanceof ClassTree clazz)
|
||||
? String.join(": ", String.valueOf(tree.getKind()), clazz.getSimpleName())
|
||||
: tree.getKind().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class ExtractionParameters {
|
||||
abstract String className();
|
||||
|
||||
abstract ImmutableList<String> path();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
final class JsonTest {
|
||||
private static final TestObject TEST_OBJECT = new AutoValue_JsonTest_TestObject("foo", 42);
|
||||
private static final String TEST_JSON = "{\"string\":\"foo\",\"number\":42}";
|
||||
|
||||
@Test
|
||||
void write(@TempDir Path directory) {
|
||||
Path file = directory.resolve("test.json");
|
||||
|
||||
Json.write(file, TEST_OBJECT);
|
||||
|
||||
assertThat(file).content(UTF_8).isEqualTo(TEST_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeFailure(@TempDir Path directory) {
|
||||
assertThatThrownBy(() -> Json.write(directory, TEST_OBJECT))
|
||||
.isInstanceOf(UncheckedIOException.class)
|
||||
.hasMessageContaining("Failure writing to '%s'", directory)
|
||||
.hasCauseInstanceOf(FileNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void read(@TempDir Path directory) throws IOException {
|
||||
Path file = directory.resolve("test.json");
|
||||
|
||||
Files.writeString(file, TEST_JSON, UTF_8);
|
||||
|
||||
assertThat(Json.read(file, TestObject.class)).isEqualTo(TEST_OBJECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readFailure(@TempDir Path directory) {
|
||||
assertThatThrownBy(() -> Json.read(directory, TestObject.class))
|
||||
.isInstanceOf(UncheckedIOException.class)
|
||||
.hasMessageContaining("Failure reading from '%s'", directory)
|
||||
.hasCauseInstanceOf(FileNotFoundException.class);
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_JsonTest_TestObject.class)
|
||||
abstract static class TestObject {
|
||||
abstract String string();
|
||||
|
||||
abstract int number();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"fullyQualifiedName": "pkg.CompleteBugChecker",
|
||||
"name": "OtherName",
|
||||
"altNames": [
|
||||
"Check"
|
||||
],
|
||||
"link": "https://error-prone.picnic.tech",
|
||||
"tags": [
|
||||
"Simplification"
|
||||
],
|
||||
"summary": "CompleteBugChecker summary",
|
||||
"explanation": "Example explanation",
|
||||
"severityLevel": "SUGGESTION",
|
||||
"canDisable": false,
|
||||
"suppressionAnnotations": [
|
||||
"com.google.errorprone.BugPattern",
|
||||
"org.junit.jupiter.api.Test"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"fullyQualifiedName": "pkg.MinimalBugChecker",
|
||||
"name": "MinimalBugChecker",
|
||||
"altNames": [],
|
||||
"link": "",
|
||||
"tags": [],
|
||||
"summary": "MinimalBugChecker summary",
|
||||
"explanation": "",
|
||||
"severityLevel": "ERROR",
|
||||
"canDisable": true,
|
||||
"suppressionAnnotations": [
|
||||
"java.lang.SuppressWarnings"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"fullyQualifiedName": "pkg.UndocumentedSuppressionBugPattern",
|
||||
"name": "UndocumentedSuppressionBugPattern",
|
||||
"altNames": [],
|
||||
"link": "",
|
||||
"tags": [],
|
||||
"summary": "UndocumentedSuppressionBugPattern summary",
|
||||
"explanation": "",
|
||||
"severityLevel": "WARNING",
|
||||
"canDisable": true,
|
||||
"suppressionAnnotations": []
|
||||
}
|
||||
@@ -24,6 +24,7 @@ project:
|
||||
|
||||
- Document how to apply patches.
|
||||
- Document each of the checks.
|
||||
- Add [SonarQube][sonarcloud] and [Codecov][codecov] integrations.
|
||||
- Add non-Java file formatting support, like we have internally at Picnic.
|
||||
(I.e., somehow open-source that stuff.)
|
||||
- Auto-generate a website listing each of the checks, just like the Error Prone
|
||||
@@ -125,6 +126,9 @@ The following is a list of checks we'd like to see implemented:
|
||||
statement. Idem for other exception types.
|
||||
- A Guava-specific check that replaces simple anonymous `CacheLoader` subclass
|
||||
declarations with `CacheLoader.from(someLambda)`.
|
||||
- A Spring-specific check that enforces that methods with the `@Scheduled`
|
||||
annotation are also annotated with New Relic's `@Trace` annotation. Such
|
||||
methods should ideally not also represent Spring MVC endpoints.
|
||||
- A Spring-specific check that enforces that `@RequestMapping` annotations,
|
||||
when applied to a method, explicitly specify one or more target HTTP methods.
|
||||
- A Spring-specific check that looks for classes in which all `@RequestMapping`
|
||||
@@ -269,6 +273,7 @@ Refaster's expressiveness:
|
||||
[autorefactor]: https://autorefactor.org
|
||||
[bettercodehub]: https://bettercodehub.com
|
||||
[checkstyle-external-project-tests]: https://github.com/checkstyle/checkstyle/blob/master/wercker.yml
|
||||
[codecov]: https://codecov.io
|
||||
[error-prone-bug-patterns]: https://errorprone.info/bugpatterns
|
||||
[error-prone]: https://errorprone.info
|
||||
[error-prone-repo]: https://github.com/google/error-prone
|
||||
@@ -278,3 +283,4 @@ Refaster's expressiveness:
|
||||
[main-contributing]: ../CONTRIBUTING.md
|
||||
[main-readme]: ../README.md
|
||||
[modernizer-maven-plugin]: https://github.com/gaul/modernizer-maven-plugin
|
||||
[sonarcloud]: https://sonarcloud.io
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.17.0</version>
|
||||
<version>0.8.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
|
||||
<name>Picnic :: Error Prone Support :: Contrib</name>
|
||||
<description>Extra Error Prone plugins by Picnic.</description>
|
||||
<url>https://error-prone.picnic.tech</url>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
@@ -38,7 +37,7 @@
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_test_helpers</artifactId>
|
||||
<scope>provided</scope>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
@@ -48,10 +47,6 @@
|
||||
`annotationProcessorPaths` configuration below. -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
@@ -65,6 +60,11 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -73,15 +73,19 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.newrelic.agent.java</groupId>
|
||||
<artifactId>newrelic-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
@@ -122,16 +126,6 @@
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.inject</groupId>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
@@ -152,6 +146,10 @@
|
||||
<artifactId>value-annotations</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jooq</groupId>
|
||||
<artifactId>jooq</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
@@ -162,8 +160,6 @@
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- XXX: Explicitly declared as a workaround for
|
||||
https://github.com/pitest/pitest-junit5-plugin/issues/105. -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
@@ -179,36 +175,6 @@
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-11</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-templating</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reactivestreams</groupId>
|
||||
<artifactId>reactive-streams</artifactId>
|
||||
@@ -244,11 +210,6 @@
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
<artifactId>testng</artifactId>
|
||||
@@ -264,9 +225,6 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths combine.children="append">
|
||||
<!-- XXX: Drop the version declarations once
|
||||
properly supported. See
|
||||
https://youtrack.jetbrains.com/issue/IDEA-342187. -->
|
||||
<path>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>documentation-support</artifactId>
|
||||
@@ -284,10 +242,22 @@
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs combine.children="append">
|
||||
<arg>-Xplugin:RefasterRuleCompiler</arg>
|
||||
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<configuration>
|
||||
<ignoredUnusedDeclaredDependencies>
|
||||
<!-- XXX: Figure out why the plugin thinks this
|
||||
dependency is unused. -->
|
||||
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support</ignoredUnusedDeclaredDependency>
|
||||
</ignoredUnusedDeclaredDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
@@ -4,7 +4,7 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -18,7 +18,7 @@ import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import java.util.Map;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
@@ -46,7 +46,7 @@ public final class AmbiguousJsonCreator extends BugChecker implements Annotation
|
||||
}
|
||||
|
||||
ClassTree clazz = state.findEnclosing(ClassTree.class);
|
||||
if (clazz == null || clazz.getKind() != Kind.ENUM) {
|
||||
if (clazz == null || clazz.getKind() != Tree.Kind.ENUM) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import static com.google.errorprone.matchers.Matchers.argument;
|
||||
import static com.google.errorprone.matchers.Matchers.argumentCount;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.nullLiteral;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
|
||||
@@ -6,9 +6,10 @@ import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -16,14 +17,13 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.List;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -48,18 +48,20 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
MultiMatchResult<AnnotationTree> hasAutowiredAnnotation =
|
||||
AUTOWIRED_ANNOTATION.multiMatchResult(Iterables.getOnlyElement(constructors), state);
|
||||
if (!hasAutowiredAnnotation.matches()) {
|
||||
ImmutableList<AnnotationTree> annotations =
|
||||
AUTOWIRED_ANNOTATION
|
||||
.multiMatchResult(Iterables.getOnlyElement(constructors), state)
|
||||
.matchingNodes();
|
||||
if (annotations.size() != 1) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the only `@Autowired` constructor: suggest that it be removed. Note that this likely
|
||||
* means that the associated import can be removed as well. Rather than adding code for this
|
||||
* case we leave flagging the unused import to Error Prone's `RemoveUnusedImports` check.
|
||||
* means that the associated import can be removed as well. Rather than adding code for this case we
|
||||
* leave flagging the unused import to Error Prone's `RemoveUnusedImports` check.
|
||||
*/
|
||||
AnnotationTree annotation = hasAutowiredAnnotation.onlyMatchingNode();
|
||||
AnnotationTree annotation = Iterables.getOnlyElement(annotations);
|
||||
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +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 tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -19,13 +19,14 @@ 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 com.sun.source.tree.Tree.Kind;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags annotations that could be written more concisely. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -90,8 +91,8 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
ExpressionTree arg = args.get(0);
|
||||
if (state.getSourceForNode(arg) == null) {
|
||||
/*
|
||||
* The annotation argument doesn't have a source representation, e.g. because `value` isn't
|
||||
* assigned explicitly.
|
||||
* The annotation argument isn't doesn't have a source representation, e.g. because `value`
|
||||
* isn't assigned to explicitly.
|
||||
*/
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -118,7 +119,7 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
* the expression as a whole.
|
||||
*/
|
||||
ExpressionTree value =
|
||||
(arg instanceof AssignmentTree assignment) ? assignment.getExpression() : arg;
|
||||
(arg.getKind() == Kind.ASSIGNMENT) ? ((AssignmentTree) arg).getExpression() : arg;
|
||||
|
||||
/* Store a fix for each expression that was successfully simplified. */
|
||||
simplifyAttributeValue(value, state)
|
||||
@@ -129,10 +130,13 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
}
|
||||
|
||||
private static Optional<String> simplifyAttributeValue(ExpressionTree expr, VisitorState state) {
|
||||
/* Drop curly braces or commas if possible. */
|
||||
return expr instanceof NewArrayTree newArray
|
||||
? simplifySingletonArray(newArray, state).or(() -> dropTrailingComma(newArray, state))
|
||||
: Optional.empty();
|
||||
if (expr.getKind() != Kind.NEW_ARRAY) {
|
||||
/* There are no curly braces or commas to be dropped here. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
NewArrayTree array = (NewArrayTree) expr;
|
||||
return simplifySingletonArray(array, state).or(() -> dropTrailingComma(array, state));
|
||||
}
|
||||
|
||||
/** Returns the expression describing the array's sole element, if any. */
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anything;
|
||||
import static com.google.errorprone.matchers.Matchers.classLiteral;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.receiverOfInvocation;
|
||||
import static com.google.errorprone.matchers.Matchers.toType;
|
||||
import static tech.picnic.errorprone.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.annotations.Var;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.BinaryTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags invocations of {@link Class#getName()} where {@link
|
||||
* Class#getCanonicalName()} was likely meant.
|
||||
*
|
||||
* <p>For top-level types these two methods generally return the same result, but for nested types
|
||||
* the former separates identifiers using a dollar sign ({@code $}) rather than a dot ({@code .}).
|
||||
*
|
||||
* @implNote This check currently only flags {@link Class#getName()} invocations on class literals,
|
||||
* and doesn't flag method references. This avoids false positives, such as suggesting use of
|
||||
* {@link Class#getCanonicalName()} in contexts where the canonical name is {@code null}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "This code should likely use the type's canonical name",
|
||||
link = BUG_PATTERNS_BASE_URL + "CanonicalClassNameUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class CanonicalClassNameUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> GET_NAME_INVOCATION =
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
receiverOfInvocation(classLiteral(anything())),
|
||||
instanceMethod().onExactClass(Class.class.getCanonicalName()).named("getName")));
|
||||
private static final Pattern CANONICAL_NAME_USING_TYPES =
|
||||
Pattern.compile("(com\\.google\\.errorprone|tech\\.picnic\\.errorprone)\\..*");
|
||||
|
||||
/** Instantiates a new {@link CanonicalClassNameUsage} instance. */
|
||||
public CanonicalClassNameUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!GET_NAME_INVOCATION.matches(tree, state) || !isPassedToCanonicalNameUsingType(state)) {
|
||||
/*
|
||||
* This is not a `class.getName()` invocation of which the result is passed to another method
|
||||
* known to accept canonical type names.
|
||||
*/
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree, SuggestedFixes.renameMethodInvocation(tree, "getCanonicalName", state));
|
||||
}
|
||||
|
||||
private static boolean isPassedToCanonicalNameUsingType(VisitorState state) {
|
||||
@Var TreePath path = state.getPath().getParentPath();
|
||||
while (path.getLeaf() instanceof BinaryTree) {
|
||||
path = path.getParentPath();
|
||||
}
|
||||
|
||||
return path.getLeaf() instanceof MethodInvocationTree methodInvocation
|
||||
&& isOwnedByCanonicalNameUsingType(ASTHelpers.getSymbol(methodInvocation));
|
||||
}
|
||||
|
||||
private static boolean isOwnedByCanonicalNameUsingType(MethodSymbol symbol) {
|
||||
return CANONICAL_NAME_USING_TYPES.matcher(symbol.owner.getQualifiedName()).matches();
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,9 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -20,12 +17,8 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
import tech.picnic.errorprone.utils.ThirdPartyLibrary;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
|
||||
@@ -37,7 +30,7 @@ import tech.picnic.errorprone.utils.ThirdPartyLibrary;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"Avoid `Collectors.to{List,Map,Set}` in favor of collectors that emphasize (im)mutability",
|
||||
"Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability",
|
||||
link = BUG_PATTERNS_BASE_URL + "CollectorMutability",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
@@ -45,7 +38,7 @@ import tech.picnic.errorprone.utils.ThirdPartyLibrary;
|
||||
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
|
||||
staticMethod().onClass(Collectors.class.getCanonicalName());
|
||||
staticMethod().onClass("java.util.stream.Collectors");
|
||||
private static final Matcher<ExpressionTree> LIST_COLLECTOR =
|
||||
staticMethod().anyClass().named("toList");
|
||||
private static final Matcher<ExpressionTree> MAP_COLLECTOR =
|
||||
@@ -65,10 +58,7 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
if (LIST_COLLECTOR.matches(tree, state)) {
|
||||
return suggestToCollectionAlternatives(
|
||||
tree,
|
||||
ImmutableList.class.getCanonicalName() + ".toImmutableList",
|
||||
ArrayList.class.getCanonicalName(),
|
||||
state);
|
||||
tree, "com.google.common.collect.ImmutableList.toImmutableList", "ArrayList", state);
|
||||
}
|
||||
|
||||
if (MAP_COLLECTOR.matches(tree, state)) {
|
||||
@@ -77,10 +67,7 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
if (SET_COLLECTOR.matches(tree, state)) {
|
||||
return suggestToCollectionAlternatives(
|
||||
tree,
|
||||
ImmutableSet.class.getCanonicalName() + ".toImmutableSet",
|
||||
HashSet.class.getCanonicalName(),
|
||||
state);
|
||||
tree, "com.google.common.collect.ImmutableSet.toImmutableSet", "HashSet", state);
|
||||
}
|
||||
|
||||
return Description.NO_MATCH;
|
||||
@@ -88,20 +75,20 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
|
||||
private Description suggestToCollectionAlternatives(
|
||||
MethodInvocationTree tree,
|
||||
String immutableReplacement,
|
||||
String fullyQualifiedImmutableReplacement,
|
||||
String mutableReplacement,
|
||||
VisitorState state) {
|
||||
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
|
||||
String toCollectionSelect =
|
||||
SuggestedFixes.qualifyStaticImport(
|
||||
Collectors.class.getCanonicalName() + ".toCollection", mutableFix, state);
|
||||
String mutableCollection = SuggestedFixes.qualifyType(state, mutableFix, mutableReplacement);
|
||||
"java.util.stream.Collectors.toCollection", mutableFix, state);
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(replaceMethodInvocation(tree, immutableReplacement, state))
|
||||
.addFix(replaceMethodInvocation(tree, fullyQualifiedImmutableReplacement, state))
|
||||
.addFix(
|
||||
mutableFix
|
||||
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableCollection))
|
||||
.addImport(String.format("java.util.%s", mutableReplacement))
|
||||
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableReplacement))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
@@ -112,20 +99,17 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
|
||||
String hashMap =
|
||||
SuggestedFixes.qualifyType(state, mutableFix, HashMap.class.getCanonicalName());
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(
|
||||
replaceMethodInvocation(
|
||||
tree, ImmutableMap.class.getCanonicalName() + ".toImmutableMap", state))
|
||||
tree, "com.google.common.collect.ImmutableMap.toImmutableMap", state))
|
||||
.addFix(
|
||||
mutableFix
|
||||
SuggestedFix.builder()
|
||||
.addImport("java.util.HashMap")
|
||||
.postfixWith(
|
||||
tree.getArguments().get(argCount - 1),
|
||||
(argCount == 2 ? ", (a, b) -> { throw new IllegalStateException(); }" : "")
|
||||
+ String.format(", %s::new", hashMap))
|
||||
+ ", HashMap::new")
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.argument;
|
||||
import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
import static com.google.errorprone.matchers.Matchers.isVariable;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.Matchers.returnStatement;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.toType;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.BlockTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.BlockTree;
|
||||
import com.sun.source.tree.ExpressionStatementTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.tree.StatementTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.TryTree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags unnecessary local variable assignments preceding a return
|
||||
* statement.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Variable assignment is redundant; value can be returned directly",
|
||||
link = BUG_PATTERNS_BASE_URL + "DirectReturn",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<StatementTree> VARIABLE_RETURN = returnStatement(isVariable());
|
||||
private static final Matcher<ExpressionTree> MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE =
|
||||
allOf(
|
||||
not(
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
argument(0, isSameType(Class.class.getCanonicalName())))),
|
||||
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
|
||||
|
||||
/** Instantiates a new {@link DirectReturn} instance. */
|
||||
public DirectReturn() {}
|
||||
|
||||
@Override
|
||||
public Description matchBlock(BlockTree tree, VisitorState state) {
|
||||
List<? extends StatementTree> statements = tree.getStatements();
|
||||
if (statements.size() < 2) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
StatementTree finalStatement = statements.get(statements.size() - 1);
|
||||
if (!VARIABLE_RETURN.matches(finalStatement, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Symbol variableSymbol = ASTHelpers.getSymbol(((ReturnTree) finalStatement).getExpression());
|
||||
StatementTree precedingStatement = statements.get(statements.size() - 2);
|
||||
|
||||
return tryMatchAssignment(variableSymbol, precedingStatement)
|
||||
.filter(
|
||||
resultExpr ->
|
||||
canInlineToReturnStatement(resultExpr, state)
|
||||
&& !isIdentifierSymbolReferencedInAssociatedFinallyBlock(variableSymbol, state))
|
||||
.map(
|
||||
resultExpr ->
|
||||
describeMatch(
|
||||
precedingStatement,
|
||||
SuggestedFix.builder()
|
||||
.replace(
|
||||
precedingStatement,
|
||||
String.format("return %s;", SourceCode.treeToString(resultExpr, state)))
|
||||
.delete(finalStatement)
|
||||
.build()))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<ExpressionTree> tryMatchAssignment(Symbol targetSymbol, Tree tree) {
|
||||
if (tree instanceof ExpressionStatementTree expressionStatement) {
|
||||
return tryMatchAssignment(targetSymbol, expressionStatement.getExpression());
|
||||
}
|
||||
|
||||
if (tree instanceof AssignmentTree assignment) {
|
||||
return targetSymbol.equals(ASTHelpers.getSymbol(assignment.getVariable()))
|
||||
? Optional.of(assignment.getExpression())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
if (tree instanceof VariableTree declaration) {
|
||||
return declaration.getModifiers().getAnnotations().isEmpty()
|
||||
&& targetSymbol.equals(ASTHelpers.getSymbol(declaration))
|
||||
? Optional.ofNullable(declaration.getInitializer())
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether inlining the given expression to the associated return statement can likely be
|
||||
* done without changing the expression's return type.
|
||||
*
|
||||
* <p>Inlining an expression generally does not change its return type, but in rare cases the
|
||||
* operation may have a functional impact. The sole case considered here is the inlining of a
|
||||
* Mockito mock or spy construction without an explicit type. In such a case the type created
|
||||
* depends on context, such as the method's return type.
|
||||
*/
|
||||
private static boolean canInlineToReturnStatement(
|
||||
ExpressionTree expressionTree, VisitorState state) {
|
||||
return !MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE.matches(expressionTree, state)
|
||||
|| MoreASTHelpers.findMethodExitedOnReturn(state)
|
||||
.filter(m -> MoreASTHelpers.areSameType(expressionTree, m.getReturnType(), state))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given identifier {@link Symbol} is referenced in a {@code finally} block that
|
||||
* is executed <em>after</em> control flow returns from the {@link VisitorState#getPath() current
|
||||
* location}.
|
||||
*/
|
||||
private static boolean isIdentifierSymbolReferencedInAssociatedFinallyBlock(
|
||||
Symbol symbol, VisitorState state) {
|
||||
return Streams.zip(
|
||||
Streams.stream(state.getPath()).skip(1),
|
||||
Streams.stream(state.getPath()),
|
||||
(tree, child) -> {
|
||||
if (!(tree instanceof TryTree tryTree)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BlockTree finallyBlock = tryTree.getFinallyBlock();
|
||||
return !child.equals(finallyBlock) ? finallyBlock : null;
|
||||
})
|
||||
.anyMatch(finallyBlock -> referencesIdentifierSymbol(symbol, finallyBlock));
|
||||
}
|
||||
|
||||
private static boolean referencesIdentifierSymbol(Symbol symbol, @Nullable BlockTree tree) {
|
||||
return Boolean.TRUE.equals(
|
||||
new TreeScanner<Boolean, @Nullable Void>() {
|
||||
@Override
|
||||
public Boolean visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
return symbol.equals(ASTHelpers.getSymbol(node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean reduce(Boolean r1, Boolean r2) {
|
||||
return Boolean.TRUE.equals(r1) || Boolean.TRUE.equals(r2);
|
||||
}
|
||||
}.scan(tree, null));
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAS
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -21,7 +21,7 @@ import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -36,15 +36,12 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
|
||||
private static final Matcher<Tree> PERMITTED_ANNOTATION =
|
||||
annotations(
|
||||
AT_LEAST_ONE,
|
||||
anyOf(
|
||||
isType(Override.class.getCanonicalName()),
|
||||
isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
|
||||
/** Instantiates a new {@link EmptyMethod} instance. */
|
||||
public EmptyMethod() {}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("java:S1067" /* Chaining disjunctions like this does not impact readability. */)
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (tree.getBody() == null
|
||||
|| !tree.getBody().getStatements().isEmpty()
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.typePredicateMatcher;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreTypePredicates.isSubTypeOf;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.type;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Mono#zip} and {@link Mono#zipWith} invocations with a
|
||||
* {@code Mono<Void>} or {@link Mono#empty()} argument or receiver.
|
||||
*
|
||||
* <p>When a zipped reactive stream completes empty, then the other zipped streams will be cancelled
|
||||
* (or not subscribed to), and the operation as a whole will complete empty as well. This is
|
||||
* generally not what was intended.
|
||||
*/
|
||||
// XXX: Generalize this check to also cover `Flux` zip operations.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Don't pass a `Mono<Void>` or `Mono.empty()` argument to `Mono#{zip,With}`",
|
||||
link = BUG_PATTERNS_BASE_URL + "EmptyMonoZip",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class EmptyMonoZip extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> MONO =
|
||||
Suppliers.typeFromString("reactor.core.publisher.Mono");
|
||||
private static final Matcher<ExpressionTree> MONO_ZIP_OR_ZIP_WITH =
|
||||
anyOf(
|
||||
instanceMethod().onDescendantOf(MONO).named("zipWith"),
|
||||
staticMethod().onClass(MONO).named("zip"));
|
||||
private static final Matcher<ExpressionTree> EMPTY_MONO =
|
||||
anyOf(
|
||||
staticMethod().onDescendantOf(MONO).named("empty"),
|
||||
typePredicateMatcher(isSubTypeOf(generic(MONO, type(Void.class.getCanonicalName())))));
|
||||
|
||||
/** Instantiates a new {@link EmptyMonoZip} instance. */
|
||||
public EmptyMonoZip() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!MONO_ZIP_OR_ZIP_WITH.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
if (hasEmptyReceiver(tree, state)) {
|
||||
return buildDescription(tree)
|
||||
.setMessage("Invoking `Mono#zipWith` on `Mono#empty()` or a `Mono<Void>` is a no-op")
|
||||
.build();
|
||||
}
|
||||
|
||||
if (hasEmptyArguments(tree, state)) {
|
||||
return describeMatch(tree);
|
||||
}
|
||||
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static boolean hasEmptyReceiver(MethodInvocationTree tree, VisitorState state) {
|
||||
return tree.getMethodSelect() instanceof MemberSelectTree memberSelect
|
||||
&& EMPTY_MONO.matches(memberSelect.getExpression(), state);
|
||||
}
|
||||
|
||||
private static boolean hasEmptyArguments(MethodInvocationTree tree, VisitorState state) {
|
||||
return tree.getArguments().stream().anyMatch(arg -> EMPTY_MONO.matches(arg, state));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package tech.picnic.errorprone.guidelines.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -67,12 +66,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
.named("addSourceLines"),
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
|
||||
.named("addInputLines"),
|
||||
// XXX: Add tests for `Compilation.compileWithDocumentationGenerator`. Until done, make
|
||||
// sure to update this matcher if that method's class or name is changed/moved.
|
||||
staticMethod()
|
||||
.onClass("tech.picnic.errorprone.documentation.Compilation")
|
||||
.named("compileWithDocumentationGenerator"));
|
||||
.named("addInputLines"));
|
||||
private static final Matcher<ExpressionTree> OUTPUT_SOURCE_ACCEPTING_METHOD =
|
||||
instanceMethod()
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
@@ -89,8 +83,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
}
|
||||
|
||||
List<? extends ExpressionTree> sourceLines =
|
||||
tree.getArguments()
|
||||
.subList(ASTHelpers.getSymbol(tree).params().size() - 1, tree.getArguments().size());
|
||||
tree.getArguments().subList(1, tree.getArguments().size());
|
||||
if (sourceLines.isEmpty()) {
|
||||
return buildDescription(tree).setMessage("No source code provided").build();
|
||||
}
|
||||
@@ -111,9 +104,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
String formatted;
|
||||
try {
|
||||
formatted = formatSourceCode(source, retainUnusedImports).trim();
|
||||
} catch (
|
||||
@SuppressWarnings("java:S1166" /* Stack trace not relevant. */)
|
||||
FormatterException e) {
|
||||
} catch (FormatterException e) {
|
||||
return buildDescription(methodInvocation)
|
||||
.setMessage(String.format("Source code is malformed: %s", e.getMessage()))
|
||||
.build();
|
||||
@@ -140,7 +131,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
SuggestedFix.replace(
|
||||
startPos,
|
||||
endPos,
|
||||
Splitter.on(System.lineSeparator())
|
||||
Splitter.on('\n')
|
||||
.splitToStream(formatted)
|
||||
.map(state::getConstantExpression)
|
||||
.collect(joining(", "))));
|
||||
@@ -156,18 +147,17 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
return FORMATTER.formatSource(withOptionallyRemovedImports);
|
||||
}
|
||||
|
||||
// XXX: This logic is duplicated in `BugPatternTestExtractor`. Can we do better?
|
||||
private static Optional<String> getConstantSourceCode(
|
||||
List<? extends ExpressionTree> sourceLines) {
|
||||
StringBuilder source = new StringBuilder();
|
||||
|
||||
for (ExpressionTree sourceLine : sourceLines) {
|
||||
String value = ASTHelpers.constValue(sourceLine, String.class);
|
||||
Object value = ASTHelpers.constValue(sourceLine);
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
source.append(value).append(System.lineSeparator());
|
||||
source.append(value).append('\n');
|
||||
}
|
||||
|
||||
return Optional.of(source.toString());
|
||||
@@ -7,7 +7,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -44,7 +44,7 @@ import java.util.stream.Stream;
|
||||
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
|
||||
staticMethod().onClass(Ordering.class.getCanonicalName()).named("explicit");
|
||||
staticMethod().onClass(Ordering.class.getName()).named("explicit");
|
||||
|
||||
/** Instantiates a new {@link ExplicitEnumOrdering} instance. */
|
||||
public ExplicitEnumOrdering() {}
|
||||
@@ -72,7 +72,7 @@ public final class ExplicitEnumOrdering extends BugChecker implements MethodInvo
|
||||
List<? extends ExpressionTree> expressions) {
|
||||
return expressions.stream()
|
||||
.map(ASTHelpers::getSymbol)
|
||||
.filter(s -> s != null && s.isEnum())
|
||||
.filter(Symbol::isEnum)
|
||||
.collect(
|
||||
collectingAndThen(
|
||||
toImmutableSetMultimap(Symbol::asType, Symbol::toString),
|
||||
|
||||
@@ -4,11 +4,11 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.unbound;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -50,9 +50,8 @@ import reactor.core.publisher.Flux;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; please use \
|
||||
`Flux#concatMap` or explicitly specify the desired amount of concurrency""",
|
||||
"`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; "
|
||||
+ "please use `Flux#concatMap` or explicitly specify the desired amount of concurrency",
|
||||
link = BUG_PATTERNS_BASE_URL + "FluxFlatMapUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
@@ -67,7 +66,7 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
instanceMethod()
|
||||
.onDescendantOf(FLUX)
|
||||
.namedAnyOf("flatMap", "flatMapSequential")
|
||||
.withParameters(Function.class.getCanonicalName());
|
||||
.withParameters(Function.class.getName());
|
||||
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
|
||||
VisitorState.memoize(
|
||||
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
@@ -83,8 +82,10 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
|
||||
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
|
||||
SuggestedFix concurrencyCapFix =
|
||||
SuggestedFix.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME);
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build();
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.CONCURRENCY;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.utils.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link reactor.core.publisher.Flux} operator usages that may
|
||||
* implicitly cause the calling thread to be blocked.
|
||||
*
|
||||
* <p>Note that the methods flagged here are not themselves blocking, but iterating over the
|
||||
* resulting {@link Iterable} or {@link Stream} may be.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid iterating over `Flux`es in an implicitly blocking manner",
|
||||
link = BUG_PATTERNS_BASE_URL + "FluxImplicitBlock",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = {CONCURRENCY, PERFORMANCE})
|
||||
public final class FluxImplicitBlock extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> FLUX_WITH_IMPLICIT_BLOCK =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.namedAnyOf("toIterable", "toStream")
|
||||
.withNoParameters();
|
||||
private static final Supplier<Type> STREAM =
|
||||
Suppliers.typeFromString(Stream.class.getCanonicalName());
|
||||
|
||||
/** Instantiates a new {@link FluxImplicitBlock} instance. */
|
||||
public FluxImplicitBlock() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!FLUX_WITH_IMPLICIT_BLOCK.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Description.Builder description =
|
||||
buildDescription(tree).addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()));
|
||||
if (ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)) {
|
||||
description.addFix(
|
||||
suggestBlockingElementCollection(
|
||||
tree, ImmutableList.class.getCanonicalName() + ".toImmutableList", state));
|
||||
}
|
||||
description.addFix(
|
||||
suggestBlockingElementCollection(
|
||||
tree, Collectors.class.getCanonicalName() + ".toList", state));
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
private static SuggestedFix suggestBlockingElementCollection(
|
||||
MethodInvocationTree tree, String fullyQualifiedCollectorMethod, VisitorState state) {
|
||||
SuggestedFix.Builder importSuggestion = SuggestedFix.builder();
|
||||
String replacementMethodInvocation =
|
||||
SuggestedFixes.qualifyStaticImport(fullyQualifiedCollectorMethod, importSuggestion, state);
|
||||
|
||||
boolean isStream =
|
||||
ASTHelpers.isSubtype(ASTHelpers.getResultType(tree), STREAM.get(state), state);
|
||||
String replacement =
|
||||
String.format(
|
||||
".collect(%s()).block()%s", replacementMethodInvocation, isStream ? ".stream()" : "");
|
||||
return importSuggestion.merge(replaceMethodInvocation(tree, replacement, state)).build();
|
||||
}
|
||||
|
||||
private static SuggestedFix.Builder replaceMethodInvocation(
|
||||
MethodInvocationTree tree, String replacement, VisitorState state) {
|
||||
int startPosition = state.getEndPosition(ASTHelpers.getReceiver(tree));
|
||||
int endPosition = state.getEndPosition(tree);
|
||||
|
||||
checkState(
|
||||
startPosition != Position.NOPOS && endPosition != Position.NOPOS,
|
||||
"Cannot locate method to be replaced in source code");
|
||||
|
||||
return SuggestedFix.builder().replace(startPosition, endPosition, replacement);
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,9 @@ import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -32,11 +30,10 @@ import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.util.SimpleTreeVisitor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags string concatenations that produce a format string; in such cases
|
||||
@@ -62,7 +59,6 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
public final class FormatStringConcatenation extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* AssertJ exposes varargs {@code fail} methods with a {@link Throwable}-accepting overload, the
|
||||
* latter of which should not be flagged.
|
||||
@@ -71,8 +67,7 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
anyMethod()
|
||||
.anyClass()
|
||||
.withAnyName()
|
||||
.withParameters(String.class.getCanonicalName(), Throwable.class.getCanonicalName());
|
||||
|
||||
.withParameters(String.class.getName(), Throwable.class.getName());
|
||||
// XXX: Drop some of these methods if we use Refaster to replace some with others.
|
||||
private static final Matcher<ExpressionTree> ASSERTJ_FORMAT_METHOD =
|
||||
anyOf(
|
||||
@@ -121,14 +116,14 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
private static final Matcher<ExpressionTree> GUAVA_FORMAT_METHOD =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass(Preconditions.class.getCanonicalName())
|
||||
.onClass("com.google.common.base.Preconditions")
|
||||
.namedAnyOf("checkArgument", "checkNotNull", "checkState"),
|
||||
staticMethod().onClass(Verify.class.getCanonicalName()).named("verify"));
|
||||
staticMethod().onClass("com.google.common.base.Verify").named("verify"));
|
||||
// XXX: Add `PrintWriter`, maybe others.
|
||||
private static final Matcher<ExpressionTree> JDK_FORMAT_METHOD =
|
||||
anyOf(
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format"),
|
||||
instanceMethod().onExactClass(Formatter.class.getCanonicalName()).named("format"));
|
||||
staticMethod().onClass("java.lang.String").named("format"),
|
||||
instanceMethod().onExactClass("java.util.Formatter").named("format"));
|
||||
private static final Matcher<ExpressionTree> SLF4J_FORMAT_METHOD =
|
||||
instanceMethod()
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
@@ -245,8 +240,8 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
}
|
||||
|
||||
private void appendExpression(Tree tree) {
|
||||
if (tree instanceof LiteralTree literal) {
|
||||
formatString.append(literal.getValue());
|
||||
if (tree instanceof LiteralTree) {
|
||||
formatString.append(((LiteralTree) tree).getValue());
|
||||
} else {
|
||||
formatString.append(formatSpecifier);
|
||||
formatArguments.add(tree);
|
||||
|
||||
@@ -7,20 +7,9 @@ import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableRangeMap;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -31,7 +20,6 @@ import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.Matchers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.ASTHelpers.TargetType;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
@@ -41,16 +29,12 @@ import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Types;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags redundant identity conversions. */
|
||||
// XXX: Consider detecting cases where a flagged expression is passed to a method, and where removal
|
||||
// of the identity conversion would cause a different method overload to be selected. Depending on
|
||||
// the target method such a modification may change the code's semantics or performance.
|
||||
// XXX: Also flag `Stream#map`, `Mono#map` and `Flux#map` invocations where the given transformation
|
||||
// is effectively the identity operation.
|
||||
// XXX: Also flag nullary instance method invocations that represent an identity conversion, such as
|
||||
// `Boolean#booleanValue()`, `Byte#byteValue()` and friends.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid or clarify identity conversions",
|
||||
@@ -68,22 +52,24 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
|
||||
.map(Class::getName)
|
||||
.collect(toImmutableSet()))
|
||||
.named("valueOf"),
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("valueOf"),
|
||||
staticMethod().onClass(String.class.getName()).named("valueOf"),
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
ImmutableBiMap.class.getCanonicalName(),
|
||||
ImmutableList.class.getCanonicalName(),
|
||||
ImmutableListMultimap.class.getCanonicalName(),
|
||||
ImmutableMap.class.getCanonicalName(),
|
||||
ImmutableMultimap.class.getCanonicalName(),
|
||||
ImmutableMultiset.class.getCanonicalName(),
|
||||
ImmutableRangeMap.class.getCanonicalName(),
|
||||
ImmutableRangeSet.class.getCanonicalName(),
|
||||
ImmutableSet.class.getCanonicalName(),
|
||||
ImmutableSetMultimap.class.getCanonicalName(),
|
||||
ImmutableTable.class.getCanonicalName())
|
||||
"com.google.common.collect.ImmutableBiMap",
|
||||
"com.google.common.collect.ImmutableList",
|
||||
"com.google.common.collect.ImmutableListMultimap",
|
||||
"com.google.common.collect.ImmutableMap",
|
||||
"com.google.common.collect.ImmutableMultimap",
|
||||
"com.google.common.collect.ImmutableMultiset",
|
||||
"com.google.common.collect.ImmutableRangeMap",
|
||||
"com.google.common.collect.ImmutableRangeSet",
|
||||
"com.google.common.collect.ImmutableSet",
|
||||
"com.google.common.collect.ImmutableSetMultimap",
|
||||
"com.google.common.collect.ImmutableTable")
|
||||
.named("copyOf"),
|
||||
staticMethod().onClass(Matchers.class.getCanonicalName()).namedAnyOf("allOf", "anyOf"),
|
||||
staticMethod()
|
||||
.onClass("com.google.errorprone.matchers.Matchers")
|
||||
.namedAnyOf("allOf", "anyOf"),
|
||||
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
|
||||
staticMethod()
|
||||
.onClass("reactor.core.publisher.Flux")
|
||||
@@ -124,9 +110,8 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage(
|
||||
"""
|
||||
This method invocation appears redundant; remove it or suppress this warning and add a \
|
||||
comment explaining its purpose""")
|
||||
"This method invocation appears redundant; remove it or suppress this warning and "
|
||||
+ "add a comment explaining its purpose")
|
||||
.addFix(SuggestedFix.replace(tree, SourceCode.treeToString(sourceTree, state)))
|
||||
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
|
||||
.build();
|
||||
|
||||
@@ -11,7 +11,7 @@ import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.Matchers.methodReturns;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -43,9 +43,8 @@ import javax.lang.model.element.Modifier;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be \
|
||||
annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`""",
|
||||
"`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be "
|
||||
+ "annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`",
|
||||
link = BUG_PATTERNS_BASE_URL + "ImmutablesSortedSetComparator",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
|
||||
@@ -3,7 +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 tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -16,15 +16,17 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.InstanceOfTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
|
||||
* of the form {@code T.class::isInstance}.
|
||||
*
|
||||
* @see MethodReferenceUsage
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check of the
|
||||
// `error-prone-experimental` module.
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
|
||||
@@ -40,12 +42,12 @@ public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExp
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
if (tree.getParameters().size() != 1
|
||||
|| !(tree.getBody() instanceof InstanceOfTree instanceOf)) {
|
||||
if (tree.getParameters().size() != 1 || tree.getBody().getKind() != Kind.INSTANCE_OF) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
VariableTree param = Iterables.getOnlyElement(tree.getParameters());
|
||||
InstanceOfTree instanceOf = (InstanceOfTree) tree.getBody();
|
||||
if (!ASTHelpers.getSymbol(param).equals(ASTHelpers.getSymbol(instanceOf.getExpression()))) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import static com.google.errorprone.matchers.Matchers.hasMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreJUnitMatchers.TEST_METHOD;
|
||||
import static tech.picnic.errorprone.utils.MoreMatchers.hasMetaAnnotation;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
@@ -8,9 +8,9 @@ import static com.google.errorprone.matchers.Matchers.enclosingClass;
|
||||
import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
|
||||
import static tech.picnic.errorprone.utils.MoreJUnitMatchers.TEST_METHOD;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -28,7 +28,7 @@ import com.sun.source.tree.MethodTree;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import tech.picnic.errorprone.utils.ConflictDetection;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ConflictDetection;
|
||||
|
||||
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
|
||||
// XXX: Consider introducing a class-level check that enforces that test classes:
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreMatchers.hasMetaAnnotation;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags nullary {@link
|
||||
* org.junit.jupiter.params.ParameterizedTest @ParameterizedTest} test methods.
|
||||
*
|
||||
* <p>Such tests are unnecessarily executed more than necessary. This checker suggests annotating
|
||||
* the method with {@link org.junit.jupiter.api.Test @Test}, and to drop all declared {@link
|
||||
* org.junit.jupiter.params.provider.ArgumentsSource argument sources}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Nullary JUnit test methods should not be parameterized",
|
||||
link = BUG_PATTERNS_BASE_URL + "JUnitNullaryParameterizedTestDeclaration",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class JUnitNullaryParameterizedTestDeclaration extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<MethodTree, AnnotationTree> IS_PARAMETERIZED_TEST =
|
||||
annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.ParameterizedTest"));
|
||||
private static final Matcher<AnnotationTree> IS_ARGUMENT_SOURCE =
|
||||
anyOf(
|
||||
isType("org.junit.jupiter.params.provider.ArgumentsSource"),
|
||||
isType("org.junit.jupiter.params.provider.ArgumentsSources"),
|
||||
hasMetaAnnotation("org.junit.jupiter.params.provider.ArgumentsSource"));
|
||||
|
||||
/** Instantiates a new {@link JUnitNullaryParameterizedTestDeclaration} instance. */
|
||||
public JUnitNullaryParameterizedTestDeclaration() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!tree.getParameters().isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
MultiMatchResult<AnnotationTree> isParameterizedTest =
|
||||
IS_PARAMETERIZED_TEST.multiMatchResult(tree, state);
|
||||
if (!isParameterizedTest.matches()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is vacuously parameterized. Suggest replacing `@ParameterizedTest` with `@Test`.
|
||||
* (As each method is checked independently, we cannot in general determine whether this
|
||||
* suggestion makes a `ParameterizedTest` type import obsolete; that task is left to Error
|
||||
* Prone's `RemoveUnusedImports` check.)
|
||||
*/
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
fix.merge(
|
||||
SuggestedFix.replace(
|
||||
isParameterizedTest.onlyMatchingNode(),
|
||||
'@' + SuggestedFixes.qualifyType(state, fix, "org.junit.jupiter.api.Test")));
|
||||
|
||||
/*
|
||||
* Also suggest dropping all (explicit and implicit) `@ArgumentsSource`s. No attempt is made to
|
||||
* assess whether a dropped `@MethodSource` also makes the referenced factory method(s) unused.
|
||||
*/
|
||||
tree.getModifiers().getAnnotations().stream()
|
||||
.filter(a -> IS_ARGUMENT_SOURCE.matches(a, state))
|
||||
.forEach(a -> fix.merge(SourceCode.deleteWithTrailingWhitespace(a, state)));
|
||||
|
||||
return describeMatch(tree, fix.build());
|
||||
}
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anything;
|
||||
import static com.google.errorprone.matchers.Matchers.argument;
|
||||
import static com.google.errorprone.matchers.Matchers.argumentCount;
|
||||
import static com.google.errorprone.matchers.Matchers.classLiteral;
|
||||
import static com.google.errorprone.matchers.Matchers.hasArguments;
|
||||
import static com.google.errorprone.matchers.Matchers.isPrimitiveOrBoxedPrimitiveType;
|
||||
import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.toType;
|
||||
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;
|
||||
import static tech.picnic.errorprone.utils.MoreJUnitMatchers.HAS_METHOD_SOURCE;
|
||||
import static tech.picnic.errorprone.utils.MoreJUnitMatchers.getMethodSourceFactoryNames;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.DoubleStream;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags JUnit tests with a {@link
|
||||
* org.junit.jupiter.params.provider.MethodSource} annotation that can be replaced with an
|
||||
* equivalent {@link org.junit.jupiter.params.provider.ValueSource} annotation.
|
||||
*/
|
||||
// XXX: Where applicable, also flag `@MethodSource` annotations that reference multiple value
|
||||
// factory methods (or that repeat the same value factory method multiple times).
|
||||
// XXX: Support inlining of overloaded value factory methods.
|
||||
// XXX: Support inlining of value factory methods referenced by multiple `@MethodSource`
|
||||
// annotations.
|
||||
// XXX: Support value factory return expressions of the form `Stream.of(a, b,
|
||||
// c).map(Arguments::argument)`.
|
||||
// XXX: Support simplification of test methods that accept additional injected parameters such as
|
||||
// `TestInfo`; such parameters should be ignored for the purpose of this check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `@ValueSource` over a `@MethodSource` where possible and reasonable",
|
||||
linkType = CUSTOM,
|
||||
link = BUG_PATTERNS_BASE_URL + "JUnitValueSource",
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class JUnitValueSource extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> SUPPORTED_VALUE_FACTORY_VALUES =
|
||||
anyOf(
|
||||
isArrayArgumentValueCandidate(),
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
staticMethod()
|
||||
.onClass("org.junit.jupiter.params.provider.Arguments")
|
||||
.namedAnyOf("arguments", "of"),
|
||||
argumentCount(1),
|
||||
argument(0, isArrayArgumentValueCandidate()))));
|
||||
private static final Matcher<ExpressionTree> ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
|
||||
isSingleDimensionArrayCreationWithAllElementsMatching(SUPPORTED_VALUE_FACTORY_VALUES);
|
||||
private static final Matcher<ExpressionTree> ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
Stream.class.getCanonicalName(),
|
||||
IntStream.class.getCanonicalName(),
|
||||
LongStream.class.getCanonicalName(),
|
||||
DoubleStream.class.getCanonicalName(),
|
||||
List.class.getCanonicalName(),
|
||||
Set.class.getCanonicalName(),
|
||||
ImmutableList.class.getCanonicalName(),
|
||||
ImmutableSet.class.getCanonicalName())
|
||||
.named("of"),
|
||||
hasArguments(AT_LEAST_ONE, anything()),
|
||||
hasArguments(ALL, SUPPORTED_VALUE_FACTORY_VALUES)));
|
||||
private static final Matcher<MethodTree> IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER =
|
||||
methodHasParameters(
|
||||
anyOf(
|
||||
isPrimitiveOrBoxedPrimitiveType(),
|
||||
isSameType(String.class),
|
||||
isSameType(state -> state.getSymtab().classType)));
|
||||
|
||||
/** Instantiates a new {@link JUnitValueSource} instance. */
|
||||
public JUnitValueSource() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Type parameterType = ASTHelpers.getType(Iterables.getOnlyElement(tree.getParameters()));
|
||||
|
||||
return findMethodSourceAnnotation(tree, state)
|
||||
.flatMap(
|
||||
methodSourceAnnotation ->
|
||||
getSoleLocalFactoryName(methodSourceAnnotation, tree)
|
||||
.filter(factory -> !hasSiblingReferencingValueFactory(tree, factory, state))
|
||||
.flatMap(factory -> findSiblingWithName(tree, factory, state))
|
||||
.flatMap(
|
||||
factoryMethod ->
|
||||
tryConstructValueSourceFix(
|
||||
parameterType, methodSourceAnnotation, factoryMethod, state))
|
||||
.map(fix -> describeMatch(methodSourceAnnotation, fix)))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the value factory method pointed to by the given {@code @MethodSource}
|
||||
* annotation, if it (a) is the only one and (b) is a method in the same class as the annotated
|
||||
* method.
|
||||
*/
|
||||
private static Optional<String> getSoleLocalFactoryName(
|
||||
AnnotationTree methodSourceAnnotation, MethodTree method) {
|
||||
return getElementIfSingleton(getMethodSourceFactoryNames(methodSourceAnnotation, method))
|
||||
.filter(name -> name.indexOf('#') < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given method has a sibling method in the same class that depends on the
|
||||
* specified value factory method.
|
||||
*/
|
||||
private static boolean hasSiblingReferencingValueFactory(
|
||||
MethodTree tree, String valueFactory, VisitorState state) {
|
||||
return findMatchingSibling(tree, m -> hasValueFactory(m, valueFactory, state), state)
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
private static Optional<MethodTree> findSiblingWithName(
|
||||
MethodTree tree, String methodName, VisitorState state) {
|
||||
return findMatchingSibling(tree, m -> m.getName().contentEquals(methodName), state);
|
||||
}
|
||||
|
||||
private static Optional<MethodTree> findMatchingSibling(
|
||||
MethodTree tree, Predicate<? super MethodTree> predicate, VisitorState state) {
|
||||
return state.findEnclosing(ClassTree.class).getMembers().stream()
|
||||
.filter(MethodTree.class::isInstance)
|
||||
.map(MethodTree.class::cast)
|
||||
.filter(not(tree::equals))
|
||||
.filter(predicate)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static boolean hasValueFactory(
|
||||
MethodTree tree, String valueFactoryMethodName, VisitorState state) {
|
||||
return findMethodSourceAnnotation(tree, state).stream()
|
||||
.anyMatch(
|
||||
annotation ->
|
||||
getMethodSourceFactoryNames(annotation, tree).contains(valueFactoryMethodName));
|
||||
}
|
||||
|
||||
private static Optional<AnnotationTree> findMethodSourceAnnotation(
|
||||
MethodTree tree, VisitorState state) {
|
||||
return HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes().stream().findFirst();
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix> tryConstructValueSourceFix(
|
||||
Type parameterType,
|
||||
AnnotationTree methodSourceAnnotation,
|
||||
MethodTree valueFactoryMethod,
|
||||
VisitorState state) {
|
||||
return getSingleReturnExpression(valueFactoryMethod)
|
||||
.flatMap(expression -> tryExtractValueSourceAttributeValue(expression, state))
|
||||
.map(
|
||||
valueSourceAttributeValue -> {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String valueSource =
|
||||
SuggestedFixes.qualifyType(
|
||||
state, fix, "org.junit.jupiter.params.provider.ValueSource");
|
||||
return fix.replace(
|
||||
methodSourceAnnotation,
|
||||
String.format(
|
||||
"@%s(%s = %s)",
|
||||
valueSource,
|
||||
toValueSourceAttributeName(parameterType),
|
||||
valueSourceAttributeValue))
|
||||
.delete(valueFactoryMethod)
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
// XXX: This pattern also occurs a few times inside Error Prone; contribute upstream.
|
||||
private static Optional<ExpressionTree> getSingleReturnExpression(MethodTree methodTree) {
|
||||
List<ExpressionTree> returnExpressions = new ArrayList<>();
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public @Nullable Void visitClass(ClassTree node, @Nullable Void unused) {
|
||||
/* Ignore `return` statements inside anonymous/local classes. */
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitReturn(ReturnTree node, @Nullable Void unused) {
|
||||
returnExpressions.add(node.getExpression());
|
||||
return super.visitReturn(node, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitLambdaExpression(
|
||||
LambdaExpressionTree node, @Nullable Void unused) {
|
||||
/* Ignore `return` statements inside lambda expressions. */
|
||||
return null;
|
||||
}
|
||||
}.scan(methodTree, null);
|
||||
|
||||
return getElementIfSingleton(returnExpressions);
|
||||
}
|
||||
|
||||
private static Optional<String> tryExtractValueSourceAttributeValue(
|
||||
ExpressionTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> arguments;
|
||||
if (ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
|
||||
arguments = ((MethodInvocationTree) tree).getArguments();
|
||||
} else if (ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
|
||||
arguments = ((NewArrayTree) tree).getInitializers();
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* Join the values into a comma-separated string, unwrapping `Arguments` factory method
|
||||
* invocations if applicable.
|
||||
*/
|
||||
return Optional.of(
|
||||
arguments.stream()
|
||||
.map(
|
||||
arg ->
|
||||
arg instanceof MethodInvocationTree methodInvocation
|
||||
? Iterables.getOnlyElement(methodInvocation.getArguments())
|
||||
: arg)
|
||||
.map(argument -> SourceCode.treeToString(argument, state))
|
||||
.collect(joining(", ")))
|
||||
.map(value -> arguments.size() > 1 ? String.format("{%s}", value) : value);
|
||||
}
|
||||
|
||||
private static String toValueSourceAttributeName(Type type) {
|
||||
String typeString = type.tsym.name.toString();
|
||||
|
||||
return switch (typeString) {
|
||||
case "Class" -> "classes";
|
||||
case "Character" -> "chars";
|
||||
case "Integer" -> "ints";
|
||||
default -> typeString.toLowerCase(Locale.ROOT) + 's';
|
||||
};
|
||||
}
|
||||
|
||||
private static <T> Optional<T> getElementIfSingleton(Collection<T> collection) {
|
||||
return Optional.of(collection)
|
||||
.filter(elements -> elements.size() == 1)
|
||||
.map(Iterables::getOnlyElement);
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> isSingleDimensionArrayCreationWithAllElementsMatching(
|
||||
Matcher<? super ExpressionTree> elementMatcher) {
|
||||
return (tree, state) -> {
|
||||
if (!(tree instanceof NewArrayTree newArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return newArray.getDimensions().isEmpty()
|
||||
&& !newArray.getInitializers().isEmpty()
|
||||
&& newArray.getInitializers().stream()
|
||||
.allMatch(element -> elementMatcher.matches(element, state));
|
||||
};
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> isArrayArgumentValueCandidate() {
|
||||
return anyOf(classLiteral(anything()), (tree, state) -> ASTHelpers.constValue(tree) != null);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.Comparator.naturalOrder;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -31,6 +31,7 @@ import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import com.sun.source.tree.PrimitiveTypeTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Symtab;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
@@ -39,11 +40,10 @@ 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;
|
||||
import tech.picnic.errorprone.utils.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.utils.Flags;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.Flags;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags annotation array listings which aren't sorted lexicographically.
|
||||
@@ -51,9 +51,6 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
|
||||
* resolution, and can even avoid it if two branches add the same entry.
|
||||
*/
|
||||
// XXX: In some places we declare a `@SuppressWarnings` annotation with a final value of
|
||||
// `key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict`. That entry must stay
|
||||
// last. Consider adding (generic?) support for such cases.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Where possible, sort annotation array attributes lexicographically",
|
||||
@@ -61,25 +58,22 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */)
|
||||
public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final ImmutableSet<String> BLACKLISTED_ANNOTATIONS =
|
||||
ImmutableSet.of(
|
||||
// XXX: Unless `JsonPropertyOrder#alphabetic` is true...
|
||||
// XXX: unless JsonPropertyOrder#alphabetic is true...
|
||||
"com.fasterxml.jackson.annotation.JsonPropertyOrder#value",
|
||||
"io.swagger.annotations.ApiImplicitParams#value",
|
||||
"io.swagger.v3.oas.annotations.Parameters#value",
|
||||
"javax.xml.bind.annotation.XmlType#propOrder",
|
||||
"org.springframework.context.annotation.PropertySource#value",
|
||||
"org.springframework.test.context.TestPropertySource#locations",
|
||||
"org.springframework.test.context.TestPropertySource#value",
|
||||
"picocli.CommandLine.Option#names");
|
||||
"org.springframework.test.context.TestPropertySource#value");
|
||||
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListing:";
|
||||
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
|
||||
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
|
||||
|
||||
/**
|
||||
* The splitter applied to string-typed annotation arguments prior to lexicographical sorting. By
|
||||
* splitting on {@code =}, strings that represent e.g. inline Spring property declarations are
|
||||
@@ -99,8 +93,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
@Inject
|
||||
LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
|
||||
public LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
|
||||
matcher = createAnnotationAttributeMatcher(flags);
|
||||
}
|
||||
|
||||
@@ -125,9 +118,13 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
}
|
||||
|
||||
private static Optional<NewArrayTree> extractArray(ExpressionTree expr) {
|
||||
return expr instanceof AssignmentTree assignment
|
||||
? extractArray(assignment.getExpression())
|
||||
: Optional.of(expr).filter(NewArrayTree.class::isInstance).map(NewArrayTree.class::cast);
|
||||
if (expr.getKind() == Kind.ASSIGNMENT) {
|
||||
return extractArray(((AssignmentTree) expr).getExpression());
|
||||
}
|
||||
|
||||
return Optional.of(expr)
|
||||
.filter(e -> e.getKind() == Kind.NEW_ARRAY)
|
||||
.map(NewArrayTree.class::cast);
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> suggestSorting(
|
||||
@@ -192,24 +189,24 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
@Override
|
||||
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getName().toString()));
|
||||
return super.visitIdentifier(node, null);
|
||||
return super.visitIdentifier(node, unused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitLiteral(LiteralTree node, @Nullable Void unused) {
|
||||
Object value = ASTHelpers.constValue(node);
|
||||
nodes.add(
|
||||
value instanceof String str
|
||||
? STRING_ARGUMENT_SPLITTER.splitToStream(str).collect(toImmutableList())
|
||||
value instanceof String
|
||||
? STRING_ARGUMENT_SPLITTER.splitToStream((String) value).collect(toImmutableList())
|
||||
: ImmutableList.of(String.valueOf(value)));
|
||||
|
||||
return super.visitLiteral(node, null);
|
||||
return super.visitLiteral(node, unused);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString()));
|
||||
return super.visitPrimitiveType(node, null);
|
||||
return super.visitPrimitiveType(node, unused);
|
||||
}
|
||||
}.scan(array, null);
|
||||
|
||||
@@ -219,10 +216,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
private static AnnotationAttributeMatcher createAnnotationAttributeMatcher(
|
||||
ErrorProneFlags flags) {
|
||||
return AnnotationAttributeMatcher.create(
|
||||
flags.get(INCLUDED_ANNOTATIONS_FLAG).isPresent()
|
||||
? Optional.of(flags.getListOrEmpty(INCLUDED_ANNOTATIONS_FLAG))
|
||||
: Optional.empty(),
|
||||
excludedAnnotations(flags));
|
||||
flags.getList(INCLUDED_ANNOTATIONS_FLAG), excludedAnnotations(flags));
|
||||
}
|
||||
|
||||
private static ImmutableList<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.DECLARATION;
|
||||
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.TYPE;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -28,7 +28,7 @@ import com.sun.tools.javac.code.TypeAnnotations.AnnotationType;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags annotations that are not lexicographically sorted.
|
||||
@@ -36,10 +36,6 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
|
||||
* resolution, and can even avoid it if two branches add the same annotation.
|
||||
*/
|
||||
// XXX: Currently this checker only flags method-level annotations. It should likely also flag
|
||||
// type-, field- and parameter-level annotations.
|
||||
// XXX: Duplicate entries are often a mistake. Consider introducing a similar `BugChecker` that
|
||||
// flags duplicates.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Sort annotations lexicographically where possible",
|
||||
@@ -50,20 +46,9 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
public final class LexicographicalAnnotationListing extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* A comparator that minimally reorders {@link AnnotationType}s, such that declaration annotations
|
||||
* are placed before type annotations.
|
||||
*/
|
||||
@SuppressWarnings({
|
||||
"java:S1067",
|
||||
"java:S3358"
|
||||
} /* Avoiding the nested ternary operator hurts readability. */)
|
||||
private static final Comparator<@Nullable AnnotationType> BY_ANNOTATION_TYPE =
|
||||
(a, b) ->
|
||||
(a == null || a == DECLARATION) && b == TYPE
|
||||
? -1
|
||||
: (a == TYPE && b == DECLARATION ? 1 : 0);
|
||||
(a == null || a == DECLARATION) && b == TYPE ? -1 : a == TYPE && b == DECLARATION ? 1 : 0;
|
||||
|
||||
/** Instantiates a new {@link LexicographicalAnnotationListing} instance. */
|
||||
public LexicographicalAnnotationListing() {}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package tech.picnic.errorprone.experimental.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -27,6 +27,7 @@ import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.ParenthesizedTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
@@ -36,6 +37,8 @@ import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
|
||||
*
|
||||
* @see IsInstanceLambdaUsage
|
||||
*/
|
||||
// XXX: Other custom expressions we could rewrite:
|
||||
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
|
||||
@@ -51,8 +54,7 @@ import javax.lang.model.element.Name;
|
||||
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
|
||||
// XXX: Expressions of the form `i -> SomeType.class.isInstance(i)` are not replaced; fix that using
|
||||
// a suitable generalization.
|
||||
// XXX: Consider folding the `IsInstanceLambdaUsage` check of the `error-prone-contrib` module into
|
||||
// this class.
|
||||
// XXX: Consider folding the `IsInstanceLambdaUsage` check into this class.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer method references over lambda expressions",
|
||||
@@ -83,19 +85,22 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, Tree subTree) {
|
||||
return switch (subTree.getKind()) {
|
||||
case BLOCK -> constructMethodRef(lambdaExpr, (BlockTree) subTree);
|
||||
case EXPRESSION_STATEMENT ->
|
||||
constructMethodRef(lambdaExpr, ((ExpressionStatementTree) subTree).getExpression());
|
||||
case METHOD_INVOCATION -> constructMethodRef(lambdaExpr, (MethodInvocationTree) subTree);
|
||||
case PARENTHESIZED ->
|
||||
constructMethodRef(lambdaExpr, ((ParenthesizedTree) subTree).getExpression());
|
||||
case RETURN -> constructMethodRef(lambdaExpr, ((ReturnTree) subTree).getExpression());
|
||||
default -> Optional.empty();
|
||||
};
|
||||
switch (subTree.getKind()) {
|
||||
case BLOCK:
|
||||
return constructMethodRef(lambdaExpr, (BlockTree) subTree);
|
||||
case EXPRESSION_STATEMENT:
|
||||
return constructMethodRef(lambdaExpr, ((ExpressionStatementTree) subTree).getExpression());
|
||||
case METHOD_INVOCATION:
|
||||
return constructMethodRef(lambdaExpr, (MethodInvocationTree) subTree);
|
||||
case PARENTHESIZED:
|
||||
return constructMethodRef(lambdaExpr, ((ParenthesizedTree) subTree).getExpression());
|
||||
case RETURN:
|
||||
return constructMethodRef(lambdaExpr, ((ReturnTree) subTree).getExpression());
|
||||
default:
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
@@ -113,35 +118,32 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
.flatMap(expectedInstance -> constructMethodRef(lambdaExpr, subTree, expectedInstance));
|
||||
}
|
||||
|
||||
// XXX: Review whether to use switch pattern matching once the targeted JDK supports this.
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr,
|
||||
MethodInvocationTree subTree,
|
||||
Optional<Name> expectedInstance) {
|
||||
ExpressionTree methodSelect = subTree.getMethodSelect();
|
||||
|
||||
if (methodSelect instanceof IdentifierTree) {
|
||||
if (expectedInstance.isPresent()) {
|
||||
/* Direct method call; there is no matching "implicit parameter". */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Symbol sym = ASTHelpers.getSymbol(methodSelect);
|
||||
return ASTHelpers.isStatic(sym)
|
||||
? constructFix(lambdaExpr, sym.owner, methodSelect)
|
||||
: constructFix(lambdaExpr, "this", methodSelect);
|
||||
switch (methodSelect.getKind()) {
|
||||
case IDENTIFIER:
|
||||
if (expectedInstance.isPresent()) {
|
||||
/* Direct method call; there is no matching "implicit parameter". */
|
||||
return Optional.empty();
|
||||
}
|
||||
Symbol sym = ASTHelpers.getSymbol(methodSelect);
|
||||
if (!ASTHelpers.isStatic(sym)) {
|
||||
return constructFix(lambdaExpr, "this", methodSelect);
|
||||
}
|
||||
return constructFix(lambdaExpr, sym.owner, methodSelect);
|
||||
case MEMBER_SELECT:
|
||||
return constructMethodRef(lambdaExpr, (MemberSelectTree) methodSelect, expectedInstance);
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind());
|
||||
}
|
||||
|
||||
if (methodSelect instanceof MemberSelectTree memberSelect) {
|
||||
return constructMethodRef(lambdaExpr, memberSelect, expectedInstance);
|
||||
}
|
||||
|
||||
throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind());
|
||||
}
|
||||
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, MemberSelectTree subTree, Optional<Name> expectedInstance) {
|
||||
if (!(subTree.getExpression() instanceof IdentifierTree identifier)) {
|
||||
if (subTree.getExpression().getKind() != Kind.IDENTIFIER) {
|
||||
// XXX: Could be parenthesized. Handle. Also in other classes.
|
||||
/*
|
||||
* Only suggest a replacement if the method select's expression provably doesn't have
|
||||
@@ -150,12 +152,12 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Name lhs = identifier.getName();
|
||||
Name lhs = ((IdentifierTree) subTree.getExpression()).getName();
|
||||
if (expectedInstance.isEmpty()) {
|
||||
return constructFix(lambdaExpr, lhs, subTree.getIdentifier());
|
||||
}
|
||||
|
||||
Type lhsType = ASTHelpers.getType(identifier);
|
||||
Type lhsType = ASTHelpers.getType(subTree.getExpression());
|
||||
if (lhsType == null || !expectedInstance.orElseThrow().equals(lhs)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -180,8 +182,8 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
ExpressionTree arg = args.get(i);
|
||||
if (!(arg instanceof IdentifierTree identifier)
|
||||
|| !identifier.getName().equals(expectedArguments.get(i + diff))) {
|
||||
if (arg.getKind() != Kind.IDENTIFIER
|
||||
|| !((IdentifierTree) arg).getName().equals(expectedArguments.get(i + diff))) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -193,8 +195,6 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
return tree.getParameters().stream().map(VariableTree::getName).collect(toImmutableList());
|
||||
}
|
||||
|
||||
// XXX: Resolve this suppression.
|
||||
@SuppressWarnings("UnqualifiedSuggestedFixImport")
|
||||
private static Optional<SuggestedFix.Builder> constructFix(
|
||||
LambdaExpressionTree lambdaExpr, Symbol target, Object methodName) {
|
||||
Name sName = target.getSimpleName();
|
||||
@@ -0,0 +1,59 @@
|
||||
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.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
|
||||
/** A {@link BugChecker} that flags likely missing Refaster annotations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "The Refaster rule contains a method without any Refaster annotations",
|
||||
link = BUG_PATTERNS_BASE_URL + "MissingRefasterAnnotation",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class MissingRefasterAnnotation extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<Tree, AnnotationTree> REFASTER_ANNOTATION =
|
||||
annotations(
|
||||
AT_LEAST_ONE,
|
||||
anyOf(
|
||||
isType("com.google.errorprone.refaster.annotation.Placeholder"),
|
||||
isType("com.google.errorprone.refaster.annotation.BeforeTemplate"),
|
||||
isType("com.google.errorprone.refaster.annotation.AfterTemplate")));
|
||||
|
||||
/** Instantiates a new {@link MissingRefasterAnnotation} instance. */
|
||||
public MissingRefasterAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
long methodTypes =
|
||||
tree.getMembers().stream()
|
||||
.filter(member -> member.getKind() == Tree.Kind.METHOD)
|
||||
.map(MethodTree.class::cast)
|
||||
.filter(method -> !ASTHelpers.isGeneratedConstructor(method))
|
||||
.map(method -> REFASTER_ANNOTATION.matches(method, state))
|
||||
.distinct()
|
||||
.count();
|
||||
|
||||
return methodTypes < 2 ? Description.NO_MATCH : buildDescription(tree).build();
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.argument;
|
||||
import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
import static com.google.errorprone.matchers.Matchers.isVariable;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
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.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import java.util.List;
|
||||
import tech.picnic.errorprone.utils.MoreASTHelpers;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags the use of {@link org.mockito.Mockito#mock(Class)} and {@link
|
||||
* org.mockito.Mockito#spy(Class)} where instead the type to be mocked or spied can be derived from
|
||||
* context.
|
||||
*/
|
||||
// XXX: This check currently does not flag method invocation arguments. When adding support for
|
||||
// this, consider that in some cases the type to be mocked or spied must be specified explicitly so
|
||||
// as to disambiguate between method overloads.
|
||||
// XXX: This check currently does not flag (implicit or explicit) lambda return expressions.
|
||||
// XXX: This check currently does not drop suppressions that become obsolete after the
|
||||
// suggested fix is applied; consider adding support for this.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Don't unnecessarily pass a type to Mockito's `mock(Class)` and `spy(Class)` methods",
|
||||
link = BUG_PATTERNS_BASE_URL + "MockitoMockClassReference",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class MockitoMockClassReference extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE =
|
||||
allOf(
|
||||
argument(0, allOf(isSameType(Class.class.getCanonicalName()), not(isVariable()))),
|
||||
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
|
||||
|
||||
/** Instantiates a new {@link MockitoMockClassReference} instance. */
|
||||
public MockitoMockClassReference() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE.matches(tree, state)
|
||||
|| !isTypeDerivableFromContext(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
List<? extends ExpressionTree> arguments = tree.getArguments();
|
||||
return describeMatch(tree, SuggestedFixes.removeElement(arguments.get(0), arguments, state));
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static boolean isTypeDerivableFromContext(MethodInvocationTree tree, VisitorState state) {
|
||||
Tree parent = state.getPath().getParentPath().getLeaf();
|
||||
return switch (parent.getKind()) {
|
||||
case VARIABLE ->
|
||||
!ASTHelpers.hasImplicitType((VariableTree) parent, state)
|
||||
&& MoreASTHelpers.areSameType(tree, parent, state);
|
||||
case ASSIGNMENT -> MoreASTHelpers.areSameType(tree, parent, state);
|
||||
case RETURN ->
|
||||
MoreASTHelpers.findMethodExitedOnReturn(state)
|
||||
.filter(m -> MoreASTHelpers.areSameType(tree, m.getReturnType(), state))
|
||||
.isPresent();
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,7 +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.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -18,7 +18,7 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.List;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags method invocations for which all arguments are wrapped using
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
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.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags usages of MongoDB {@code $text} filter usages.
|
||||
*
|
||||
* @see <a href="https://www.mongodb.com/docs/manual/text-search/">MongoDB Text Search</a>
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
Avoid MongoDB's `$text` filter operator, as it can trigger heavy queries and even cause \
|
||||
the server to run out of memory""",
|
||||
link = BUG_PATTERNS_BASE_URL + "MongoDBTextFilterUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = PERFORMANCE)
|
||||
public final class MongoDBTextFilterUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MONGO_FILTERS_TEXT_METHOD =
|
||||
staticMethod().onClass("com.mongodb.client.model.Filters").named("text");
|
||||
|
||||
/** Instantiates a new {@link MongoDBTextFilterUsage} instance. */
|
||||
public MongoDBTextFilterUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return MONGO_FILTERS_TEXT_METHOD.matches(tree, state)
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@ package tech.picnic.errorprone.bugpatterns;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreMatchers.isSubTypeOf;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -15,19 +14,14 @@ import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A {@link BugChecker} that flags nesting of {@link Optional Optionals}. */
|
||||
// XXX: Extend this checker to also flag method return types and variable/field types.
|
||||
// XXX: Consider generalizing this checker and `NestedPublishers` to a single `NestedMonad` check,
|
||||
// which e.g. also flags nested `Stream`s. Alternatively, combine these and other checkers into an
|
||||
// even more generic `ConfusingType` checker.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
@@ -39,16 +33,19 @@ import java.util.Optional;
|
||||
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
|
||||
private static final Matcher<Tree> IS_OPTIONAL_OF_OPTIONAL =
|
||||
isSubTypeOf(VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL)))));
|
||||
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
|
||||
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
|
||||
|
||||
/** Instantiates a new {@link NestedOptionals} instance. */
|
||||
public NestedOptionals() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return IS_OPTIONAL_OF_OPTIONAL.matches(tree, state)
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
Type type = OPTIONAL_OF_OPTIONAL.get(state);
|
||||
if (type == null || !state.getTypes().isSubtype(ASTHelpers.getType(tree), type)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(tree);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.Matchers.typePredicateMatcher;
|
||||
import static com.google.errorprone.predicates.TypePredicates.allOf;
|
||||
import static com.google.errorprone.predicates.TypePredicates.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.utils.MoreTypePredicates.hasTypeParameter;
|
||||
import static tech.picnic.errorprone.utils.MoreTypePredicates.isSubTypeOf;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.utils.MoreTypes.type;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@code Publisher<? extends Publisher>} instances, unless the
|
||||
* nested {@link org.reactivestreams.Publisher} is a {@link reactor.core.publisher.GroupedFlux}.
|
||||
*/
|
||||
// XXX: See the `NestedOptionals` check for some ideas on how to generalize this kind of checker.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
Avoid `Publisher`s that emit other `Publishers`s; the resultant code is hard to reason \
|
||||
about""",
|
||||
link = BUG_PATTERNS_BASE_URL + "NestedPublishers",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class NestedPublishers extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> PUBLISHER = type("org.reactivestreams.Publisher");
|
||||
private static final Matcher<ExpressionTree> IS_NON_GROUPED_PUBLISHER_OF_PUBLISHERS =
|
||||
typePredicateMatcher(
|
||||
allOf(
|
||||
isSubTypeOf(generic(PUBLISHER, subOf(raw(PUBLISHER)))),
|
||||
not(
|
||||
hasTypeParameter(
|
||||
0, isSubTypeOf(raw(type("reactor.core.publisher.GroupedFlux")))))));
|
||||
|
||||
/** Instantiates a new {@link NestedPublishers} instance. */
|
||||
public NestedPublishers() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return IS_NON_GROUPED_PUBLISHER_OF_PUBLISHERS.matches(tree, state)
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -20,7 +20,7 @@ import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.function.BiFunction;
|
||||
import reactor.core.publisher.Mono;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Mono} operations that are known to be vacuous, given that
|
||||
@@ -43,7 +43,6 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
// emitting a value (e.g. `Mono.empty()`, `someFlux.then()`, ...), or known not to complete normally
|
||||
// (`Mono.never()`, `someFlux.repeat()`, `Mono.error(...)`, ...). The latter category could
|
||||
// potentially be split out further.
|
||||
@SuppressWarnings("java:S1192" /* Factoring out repeated method names impacts readability. */)
|
||||
public final class NonEmptyMono extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
|
||||
@@ -72,7 +71,7 @@ public final class NonEmptyMono extends BugChecker implements MethodInvocationTr
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.named("reduce")
|
||||
.withParameters(Object.class.getCanonicalName(), BiFunction.class.getCanonicalName()),
|
||||
.withParameters(Object.class.getName(), BiFunction.class.getName()),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static tech.picnic.errorprone.bugpatterns.StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import java.time.Clock;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags static imports of type members that should *not* be statically
|
||||
* imported.
|
||||
*/
|
||||
// XXX: This check is closely linked to `StaticImport`. Consider merging the two.
|
||||
// XXX: Add suppression support. If qualification of one more more identifiers is suppressed, then
|
||||
// the associated static import should *not* be removed.
|
||||
// XXX: Also introduce logic that disallows statically importing `ZoneOffset.ofHours` and other
|
||||
// `ofXXX`-style methods.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Member should not be statically imported",
|
||||
link = BUG_PATTERNS_BASE_URL + "NonStaticImport",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class NonStaticImport extends BugChecker implements CompilationUnitTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Types whose members should not be statically imported, unless exempted by {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
*
|
||||
* <p>Types listed here should be mutually exclusive with {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_TYPES}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> NON_STATIC_IMPORT_CANDIDATE_TYPES =
|
||||
ImmutableSet.of(
|
||||
ASTHelpers.class.getCanonicalName(),
|
||||
Clock.class.getCanonicalName(),
|
||||
Strings.class.getCanonicalName(),
|
||||
VisitorState.class.getCanonicalName(),
|
||||
ZoneOffset.class.getCanonicalName(),
|
||||
"com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode",
|
||||
"reactor.core.publisher.Flux",
|
||||
"reactor.core.publisher.Mono");
|
||||
|
||||
/**
|
||||
* Type members that should never be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Types listed by {@link #NON_STATIC_IMPORT_CANDIDATE_TYPES} and members listed by {@link
|
||||
* #NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS} should be omitted from this collection.
|
||||
* <li>This collection should be mutually exclusive with {@link
|
||||
* StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
* </ul>
|
||||
*/
|
||||
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
|
||||
// method name that could be considered "too vague" or could conceivably mean something else in a
|
||||
// specific context is left out.
|
||||
static final ImmutableSetMultimap<String, String> NON_STATIC_IMPORT_CANDIDATE_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.putAll(
|
||||
Collections.class.getCanonicalName(),
|
||||
"addAll",
|
||||
"copy",
|
||||
"fill",
|
||||
"list",
|
||||
"max",
|
||||
"min",
|
||||
"nCopies",
|
||||
"rotate",
|
||||
"sort",
|
||||
"swap")
|
||||
.put(Locale.class.getCanonicalName(), "ROOT")
|
||||
.put(Optional.class.getCanonicalName(), "empty")
|
||||
.putAll(Pattern.class.getCanonicalName(), "compile", "matches", "quote")
|
||||
.put(Predicates.class.getCanonicalName(), "contains")
|
||||
.put("org.springframework.http.MediaType", "ALL")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Identifiers that should never be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Identifiers listed by {@link StaticImport#STATIC_IMPORT_CANDIDATE_MEMBERS} should be
|
||||
* mutually exclusive with identifiers listed here.
|
||||
* <li>This list should contain a superset of the identifiers flagged by {@link
|
||||
* com.google.errorprone.bugpatterns.BadImport}.
|
||||
* </ul>
|
||||
*/
|
||||
static final ImmutableSet<String> NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS =
|
||||
ImmutableSet.of(
|
||||
"builder",
|
||||
"copyOf",
|
||||
"create",
|
||||
"from",
|
||||
"getDefaultInstance",
|
||||
"INSTANCE",
|
||||
"MAX",
|
||||
"MAX_VALUE",
|
||||
"MIN",
|
||||
"MIN_VALUE",
|
||||
"newBuilder",
|
||||
"newInstance",
|
||||
"of",
|
||||
"parse",
|
||||
"valueOf");
|
||||
|
||||
/** Instantiates a new {@link NonStaticImport} instance. */
|
||||
public NonStaticImport() {}
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
ImmutableTable<String, String, UndesiredStaticImport> undesiredStaticImports =
|
||||
getUndesiredStaticImports(tree, state);
|
||||
|
||||
if (!undesiredStaticImports.isEmpty()) {
|
||||
replaceUndesiredStaticImportUsages(tree, undesiredStaticImports, state);
|
||||
|
||||
for (UndesiredStaticImport staticImport : undesiredStaticImports.values()) {
|
||||
state.reportMatch(
|
||||
describeMatch(staticImport.importTree(), staticImport.fixBuilder().build()));
|
||||
}
|
||||
}
|
||||
|
||||
/* Any violations have been flagged against the offending static import statement. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static ImmutableTable<String, String, UndesiredStaticImport> getUndesiredStaticImports(
|
||||
CompilationUnitTree tree, VisitorState state) {
|
||||
ImmutableTable.Builder<String, String, UndesiredStaticImport> imports =
|
||||
ImmutableTable.builder();
|
||||
for (ImportTree importTree : tree.getImports()) {
|
||||
Tree qualifiedIdentifier = importTree.getQualifiedIdentifier();
|
||||
if (importTree.isStatic() && qualifiedIdentifier instanceof MemberSelectTree memberSelect) {
|
||||
String type = SourceCode.treeToString(memberSelect.getExpression(), state);
|
||||
String member = memberSelect.getIdentifier().toString();
|
||||
if (shouldNotBeStaticallyImported(type, member)) {
|
||||
imports.put(
|
||||
type,
|
||||
member,
|
||||
new AutoValue_NonStaticImport_UndesiredStaticImport(
|
||||
importTree, SuggestedFix.builder().removeStaticImport(type + '.' + member)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imports.build();
|
||||
}
|
||||
|
||||
private static boolean shouldNotBeStaticallyImported(String type, String member) {
|
||||
return (NON_STATIC_IMPORT_CANDIDATE_TYPES.contains(type)
|
||||
&& !STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member))
|
||||
|| NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type, member)
|
||||
|| NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(member);
|
||||
}
|
||||
|
||||
private static void replaceUndesiredStaticImportUsages(
|
||||
CompilationUnitTree tree,
|
||||
ImmutableTable<String, String, UndesiredStaticImport> undesiredStaticImports,
|
||||
VisitorState state) {
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
Symbol symbol = ASTHelpers.getSymbol(node);
|
||||
if (symbol != null) {
|
||||
UndesiredStaticImport staticImport =
|
||||
undesiredStaticImports.get(
|
||||
symbol.owner.getQualifiedName().toString(), symbol.name.toString());
|
||||
if (staticImport != null) {
|
||||
SuggestedFix.Builder fix = staticImport.fixBuilder();
|
||||
fix.prefixWith(node, SuggestedFixes.qualifyType(state, fix, symbol.owner) + '.');
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitIdentifier(node, null);
|
||||
}
|
||||
}.scan(tree, null);
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class UndesiredStaticImport {
|
||||
abstract ImportTree importTree();
|
||||
|
||||
abstract SuggestedFix.Builder fixBuilder();
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
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.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.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.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags arguments to {@link Optional#orElse(Object)} that should be
|
||||
* deferred using {@link Optional#orElseGet(Supplier)}.
|
||||
*
|
||||
* <p>The suggested fix assumes that the argument to {@code orElse} does not have side effects. If
|
||||
* 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.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
Prefer `Optional#orElseGet` over `Optional#orElse` if the fallback requires additional \
|
||||
computation""",
|
||||
linkType = NONE,
|
||||
severity = WARNING,
|
||||
tags = PERFORMANCE)
|
||||
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
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() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!OPTIONAL_OR_ELSE_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
|
||||
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* We have a match. Construct the method reference or lambda expression to be passed to the
|
||||
* replacement `#orElseGet` invocation.
|
||||
*/
|
||||
String newArgument =
|
||||
tryMethodReferenceConversion(argument, state)
|
||||
.orElseGet(() -> "() -> " + SourceCode.treeToString(argument, state));
|
||||
|
||||
/* Construct the suggested fix, replacing the method invocation and its argument. */
|
||||
SuggestedFix fix =
|
||||
SuggestedFix.builder()
|
||||
.merge(SuggestedFixes.renameMethodInvocation(tree, "orElseGet", state))
|
||||
.replace(argument, newArgument)
|
||||
.build();
|
||||
|
||||
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) {
|
||||
if (!(tree instanceof MethodInvocationTree methodInvocation)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!methodInvocation.getArguments().isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (!(methodInvocation.getMethodSelect() instanceof MemberSelectTree memberSelect)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (requiresComputation(memberSelect.getExpression())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
SourceCode.treeToString(memberSelect.getExpression(), state)
|
||||
+ "::"
|
||||
+ (methodInvocation.getTypeArguments().isEmpty()
|
||||
? ""
|
||||
: methodInvocation.getTypeArguments().stream()
|
||||
.map(arg -> SourceCode.treeToString(arg, state))
|
||||
.collect(joining(",", "<", ">")))
|
||||
+ memberSelect.getIdentifier());
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ 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.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -17,12 +17,10 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
@@ -34,7 +32,7 @@ import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@code Comparator#comparing*} invocations that can be replaced
|
||||
@@ -45,34 +43,32 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type of \
|
||||
the provided function""",
|
||||
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
|
||||
+ " of the provided function",
|
||||
link = BUG_PATTERNS_BASE_URL + "PrimitiveComparison",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = PERFORMANCE)
|
||||
@SuppressWarnings("java:S1192" /* Factoring out repeated method names impacts readability. */)
|
||||
public final class PrimitiveComparison extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass(Comparator.class.getCanonicalName())
|
||||
.onClass(Comparator.class.getName())
|
||||
.namedAnyOf("comparingInt", "comparingLong", "comparingDouble"),
|
||||
staticMethod()
|
||||
.onClass(Comparator.class.getCanonicalName())
|
||||
.onClass(Comparator.class.getName())
|
||||
.named("comparing")
|
||||
.withParameters(Function.class.getCanonicalName()));
|
||||
.withParameters(Function.class.getName()));
|
||||
private static final Matcher<ExpressionTree> INSTANCE_COMPARISON_METHOD =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf(Comparator.class.getCanonicalName())
|
||||
.onDescendantOf(Comparator.class.getName())
|
||||
.namedAnyOf("thenComparingInt", "thenComparingLong", "thenComparingDouble"),
|
||||
instanceMethod()
|
||||
.onDescendantOf(Comparator.class.getCanonicalName())
|
||||
.onDescendantOf(Comparator.class.getName())
|
||||
.named("thenComparing")
|
||||
.withParameters(Function.class.getCanonicalName()));
|
||||
.withParameters(Function.class.getName()));
|
||||
|
||||
/** Instantiates a new {@link PrimitiveComparison} instance. */
|
||||
public PrimitiveComparison() {}
|
||||
@@ -149,44 +145,37 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
return isStatic ? "comparing" : "thenComparing";
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static Optional<Type> getPotentiallyBoxedReturnType(ExpressionTree tree) {
|
||||
if (tree instanceof LambdaExpressionTree lambdaExpression) {
|
||||
/* Return the lambda expression's actual return type. */
|
||||
return Optional.ofNullable(ASTHelpers.getType(lambdaExpression.getBody()));
|
||||
switch (tree.getKind()) {
|
||||
case LAMBDA_EXPRESSION:
|
||||
/* Return the lambda expression's actual return type. */
|
||||
return Optional.ofNullable(ASTHelpers.getType(((LambdaExpressionTree) tree).getBody()));
|
||||
case MEMBER_REFERENCE:
|
||||
/* Return the method's declared return type. */
|
||||
// XXX: Very fragile. Do better.
|
||||
Type subType2 = ((JCMemberReference) tree).referentType;
|
||||
return Optional.of(subType2.getReturnType());
|
||||
default:
|
||||
/* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// XXX: The match against a concrete type and reference to one of its fields is fragile. Do
|
||||
// better.
|
||||
if (tree instanceof JCMemberReference memberReference) {
|
||||
/* Return the method's declared return type. */
|
||||
Type subType = memberReference.referentType;
|
||||
return Optional.of(subType.getReturnType());
|
||||
}
|
||||
|
||||
/* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static Fix suggestFix(
|
||||
MethodInvocationTree tree, String preferredMethodName, VisitorState state) {
|
||||
ExpressionTree expr = tree.getMethodSelect();
|
||||
|
||||
if (expr instanceof IdentifierTree) {
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String replacement =
|
||||
SuggestedFixes.qualifyStaticImport(
|
||||
Comparator.class.getCanonicalName() + '.' + preferredMethodName, fix, state);
|
||||
return fix.replace(expr, replacement).build();
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
return SuggestedFix.builder()
|
||||
.addStaticImport(Comparator.class.getName() + '.' + preferredMethodName)
|
||||
.replace(expr, preferredMethodName)
|
||||
.build();
|
||||
case MEMBER_SELECT:
|
||||
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
|
||||
return SuggestedFix.replace(
|
||||
ms, SourceCode.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
|
||||
if (expr instanceof MemberSelectTree memberSelect) {
|
||||
return SuggestedFix.replace(
|
||||
memberSelect,
|
||||
SourceCode.treeToString(memberSelect.getExpression(), state) + '.' + preferredMethodName);
|
||||
}
|
||||
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anyMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anything;
|
||||
import static com.google.errorprone.matchers.Matchers.argumentCount;
|
||||
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
|
||||
import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
@@ -15,11 +14,9 @@ import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.primitives.Primitives;
|
||||
@@ -52,10 +49,9 @@ import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import tech.picnic.errorprone.utils.Flags;
|
||||
import tech.picnic.errorprone.utils.MethodMatcherFactory;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.Flags;
|
||||
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags redundant explicit string conversions. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -65,18 +61,15 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
@SuppressWarnings({
|
||||
"java:S1192" /* Factoring out repeated method names impacts readability. */,
|
||||
"java:S2160" /* Super class equality definition suffices. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
public final class RedundantStringConversion extends BugChecker
|
||||
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String EXTRA_STRING_CONVERSION_METHODS_FLAG =
|
||||
"RedundantStringConversion:ExtraConversionMethods";
|
||||
|
||||
private static final Matcher<ExpressionTree> ANY_EXPR = anything();
|
||||
@SuppressWarnings("UnnecessaryLambda")
|
||||
private static final Matcher<ExpressionTree> ANY_EXPR = (t, s) -> true;
|
||||
|
||||
private static final Matcher<ExpressionTree> LOCALE = isSameType(Locale.class);
|
||||
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
|
||||
private static final Matcher<ExpressionTree> STRING = isSameType(String.class);
|
||||
@@ -88,7 +81,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
private static final Matcher<MethodInvocationTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(Object.class.getCanonicalName())
|
||||
.onDescendantOfAny(Object.class.getName())
|
||||
.named("toString")
|
||||
.withNoParameters(),
|
||||
allOf(
|
||||
@@ -102,7 +95,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
.collect(toImmutableSet()))
|
||||
.named("toString"),
|
||||
allOf(
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("valueOf"),
|
||||
staticMethod().onClass(String.class.getName()).named("valueOf"),
|
||||
not(
|
||||
anyMethod()
|
||||
.anyClass()
|
||||
@@ -111,37 +104,35 @@ public final class RedundantStringConversion extends BugChecker
|
||||
ImmutableList.of(Suppliers.arrayOf(Suppliers.CHAR_TYPE))))))));
|
||||
private static final Matcher<ExpressionTree> STRINGBUILDER_APPEND_INVOCATION =
|
||||
instanceMethod()
|
||||
.onDescendantOf(StringBuilder.class.getCanonicalName())
|
||||
.onDescendantOf(StringBuilder.class.getName())
|
||||
.named("append")
|
||||
.withParameters(String.class.getCanonicalName());
|
||||
.withParameters(String.class.getName());
|
||||
private static final Matcher<ExpressionTree> STRINGBUILDER_INSERT_INVOCATION =
|
||||
instanceMethod()
|
||||
.onDescendantOf(StringBuilder.class.getCanonicalName())
|
||||
.onDescendantOf(StringBuilder.class.getName())
|
||||
.named("insert")
|
||||
.withParameters(int.class.getCanonicalName(), String.class.getCanonicalName());
|
||||
.withParameters(int.class.getName(), String.class.getName());
|
||||
private static final Matcher<ExpressionTree> FORMATTER_INVOCATION =
|
||||
anyOf(
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format"),
|
||||
instanceMethod().onDescendantOf(Formatter.class.getCanonicalName()).named("format"),
|
||||
staticMethod().onClass(String.class.getName()).named("format"),
|
||||
instanceMethod().onDescendantOf(Formatter.class.getName()).named("format"),
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(
|
||||
PrintStream.class.getCanonicalName(), PrintWriter.class.getCanonicalName())
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.namedAnyOf("format", "printf"),
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(
|
||||
PrintStream.class.getCanonicalName(), PrintWriter.class.getCanonicalName())
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.namedAnyOf("print", "println")
|
||||
.withParameters(Object.class.getCanonicalName()),
|
||||
.withParameters(Object.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Console.class.getCanonicalName())
|
||||
.onClass(Console.class.getName())
|
||||
.namedAnyOf("format", "printf", "readline", "readPassword"));
|
||||
private static final Matcher<ExpressionTree> GUAVA_GUARD_INVOCATION =
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClass(Preconditions.class.getCanonicalName())
|
||||
.onClass("com.google.common.base.Preconditions")
|
||||
.namedAnyOf("checkArgument", "checkState", "checkNotNull"),
|
||||
staticMethod()
|
||||
.onClass(Verify.class.getCanonicalName())
|
||||
.onClass("com.google.common.base.Verify")
|
||||
.namedAnyOf("verify", "verifyNotNull"));
|
||||
private static final Matcher<ExpressionTree> SLF4J_LOGGER_INVOCATION =
|
||||
instanceMethod()
|
||||
@@ -160,8 +151,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
@Inject
|
||||
RedundantStringConversion(ErrorProneFlags flags) {
|
||||
public RedundantStringConversion(ErrorProneFlags flags) {
|
||||
conversionMethodMatcher = createConversionMethodMatcher(flags);
|
||||
}
|
||||
|
||||
@@ -174,7 +164,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
ExpressionTree lhs = tree.getLeftOperand();
|
||||
ExpressionTree rhs = tree.getRightOperand();
|
||||
if (!STRING.matches(lhs, state)) {
|
||||
return createDescription(tree, tryFix(rhs, state, STRING));
|
||||
return finalize(tree, tryFix(rhs, state, STRING));
|
||||
}
|
||||
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
@@ -188,7 +178,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
}
|
||||
tryFix(rhs, state, ANY_EXPR).ifPresent(fixes::add);
|
||||
|
||||
return createDescription(tree, fixes.stream().reduce(SuggestedFix.Builder::merge));
|
||||
return finalize(tree, fixes.stream().reduce(SuggestedFix.Builder::merge));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,36 +187,36 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return createDescription(tree, tryFix(tree.getExpression(), state, ANY_EXPR));
|
||||
return finalize(tree, tryFix(tree.getExpression(), state, ANY_EXPR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (STRINGBUILDER_APPEND_INVOCATION.matches(tree, state)) {
|
||||
return createDescription(tree, tryFixPositionalConverter(tree.getArguments(), state, 0));
|
||||
return finalize(tree, tryFixPositionalConverter(tree.getArguments(), state, 0));
|
||||
}
|
||||
|
||||
if (STRINGBUILDER_INSERT_INVOCATION.matches(tree, state)) {
|
||||
return createDescription(tree, tryFixPositionalConverter(tree.getArguments(), state, 1));
|
||||
return finalize(tree, tryFixPositionalConverter(tree.getArguments(), state, 1));
|
||||
}
|
||||
|
||||
if (FORMATTER_INVOCATION.matches(tree, state)) {
|
||||
return createDescription(tree, tryFixFormatter(tree.getArguments(), state));
|
||||
return finalize(tree, tryFixFormatter(tree.getArguments(), state));
|
||||
}
|
||||
|
||||
if (GUAVA_GUARD_INVOCATION.matches(tree, state)) {
|
||||
return createDescription(tree, tryFixGuavaGuard(tree.getArguments(), state));
|
||||
return finalize(tree, tryFixGuavaGuard(tree.getArguments(), state));
|
||||
}
|
||||
|
||||
if (SLF4J_LOGGER_INVOCATION.matches(tree, state)) {
|
||||
return createDescription(tree, tryFixSlf4jLogger(tree.getArguments(), state));
|
||||
return finalize(tree, tryFixSlf4jLogger(tree.getArguments(), state));
|
||||
}
|
||||
|
||||
if (instanceMethod().matches(tree, state)) {
|
||||
return createDescription(tree, tryFix(tree, state, STRING));
|
||||
return finalize(tree, tryFix(tree, state, STRING));
|
||||
}
|
||||
|
||||
return createDescription(tree, tryFix(tree, state, NON_NULL_STRING));
|
||||
return finalize(tree, tryFix(tree, state, NON_NULL_STRING));
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> tryFixPositionalConverter(
|
||||
@@ -309,7 +299,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
|
||||
/* Simplify the values to be plugged into the format pattern, if possible. */
|
||||
return arguments.stream()
|
||||
.skip(patternIndex + 1L)
|
||||
.skip(patternIndex + 1)
|
||||
.map(arg -> tryFix(arg, state, remainingArgFilter))
|
||||
.flatMap(Optional::stream)
|
||||
.reduce(SuggestedFix.Builder::merge);
|
||||
@@ -331,32 +321,36 @@ public final class RedundantStringConversion extends BugChecker
|
||||
}
|
||||
|
||||
private Optional<ExpressionTree> trySimplify(ExpressionTree tree, VisitorState state) {
|
||||
if (!(tree instanceof MethodInvocationTree methodInvocation)) {
|
||||
if (tree.getKind() != Kind.METHOD_INVOCATION) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
|
||||
if (!conversionMethodMatcher.matches(methodInvocation, state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return switch (methodInvocation.getArguments().size()) {
|
||||
case 0 -> trySimplifyNullaryMethod(methodInvocation, state);
|
||||
case 1 -> trySimplifyUnaryMethod(methodInvocation, state);
|
||||
default ->
|
||||
throw new IllegalStateException(
|
||||
"Cannot simplify method call with two or more arguments: "
|
||||
+ SourceCode.treeToString(tree, state));
|
||||
};
|
||||
switch (methodInvocation.getArguments().size()) {
|
||||
case 0:
|
||||
return trySimplifyNullaryMethod(methodInvocation, state);
|
||||
case 1:
|
||||
return trySimplifyUnaryMethod(methodInvocation, state);
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Cannot simplify method call with two or more arguments: "
|
||||
+ SourceCode.treeToString(tree, state));
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<ExpressionTree> trySimplifyNullaryMethod(
|
||||
MethodInvocationTree methodInvocation, VisitorState state) {
|
||||
if (!instanceMethod().matches(methodInvocation, state)
|
||||
|| !(methodInvocation.getMethodSelect() instanceof MemberSelectTree memberSelect)) {
|
||||
if (!instanceMethod().matches(methodInvocation, state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(memberSelect.getExpression())
|
||||
return Optional.of(methodInvocation.getMethodSelect())
|
||||
.filter(methodSelect -> methodSelect.getKind() == Kind.MEMBER_SELECT)
|
||||
.map(methodSelect -> ((MemberSelectTree) methodSelect).getExpression())
|
||||
.filter(expr -> !"super".equals(SourceCode.treeToString(expr, state)));
|
||||
}
|
||||
|
||||
@@ -369,7 +363,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
return Optional.of(Iterables.getOnlyElement(methodInvocation.getArguments()));
|
||||
}
|
||||
|
||||
private Description createDescription(Tree tree, Optional<SuggestedFix.Builder> fixes) {
|
||||
private Description finalize(Tree tree, Optional<SuggestedFix.Builder> fixes) {
|
||||
return fixes
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package tech.picnic.errorprone.guidelines.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -17,7 +17,7 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags unnecessary {@link Refaster#anyOf(Object[])} usages.
|
||||
@@ -35,25 +35,28 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
|
||||
staticMethod().onClass(Refaster.class.getCanonicalName()).named("anyOf");
|
||||
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
|
||||
|
||||
/** Instantiates a new {@link RefasterAnyOfUsage} instance. */
|
||||
public RefasterAnyOfUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
int argumentCount = tree.getArguments().size();
|
||||
if (argumentCount > 1 || !REFASTER_ANY_OF.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
if (REFASTER_ANY_OF.matches(tree, state)) {
|
||||
switch (tree.getArguments().size()) {
|
||||
case 0:
|
||||
// We can't safely fix this case; dropping the expression may produce non-compilable code.
|
||||
return describeMatch(tree);
|
||||
case 1:
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(
|
||||
tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
|
||||
default:
|
||||
/* Handled below. */
|
||||
}
|
||||
}
|
||||
|
||||
if (argumentCount == 0) {
|
||||
/* We can't safely fix this case; dropping the expression may produce non-compilable code. */
|
||||
return describeMatch(tree);
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package tech.picnic.errorprone.guidelines.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -11,7 +11,7 @@ import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -22,10 +22,6 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.io.InputStream;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@code @RequestMapping} methods that have one or more parameters
|
||||
@@ -73,20 +69,14 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestPart"),
|
||||
isType(
|
||||
"org.springframework.security.core.annotation.CurrentSecurityContext"))),
|
||||
isSameType(InputStream.class.getCanonicalName()),
|
||||
isSameType(Locale.class.getCanonicalName()),
|
||||
isSameType(TimeZone.class.getCanonicalName()),
|
||||
isSameType(ZoneId.class.getCanonicalName()),
|
||||
isSameType("jakarta.servlet.http.HttpServletRequest"),
|
||||
isSameType("jakarta.servlet.http.HttpServletResponse"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestPart"))),
|
||||
isSameType("java.io.InputStream"),
|
||||
isSameType("java.time.ZoneId"),
|
||||
isSameType("java.util.Locale"),
|
||||
isSameType("java.util.TimeZone"),
|
||||
isSameType("javax.servlet.http.HttpServletRequest"),
|
||||
isSameType("javax.servlet.http.HttpServletResponse"),
|
||||
isSameType("org.springframework.http.HttpMethod"),
|
||||
isSameType("org.springframework.ui.Model"),
|
||||
isSameType("org.springframework.validation.BindingResult"),
|
||||
isSameType("org.springframework.web.context.request.NativeWebRequest"),
|
||||
isSameType("org.springframework.web.context.request.WebRequest"),
|
||||
isSameType("org.springframework.web.server.ServerWebExchange"),
|
||||
@@ -105,10 +95,8 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
|
||||
&& LACKS_PARAMETER_ANNOTATION.matches(tree, state)
|
||||
? buildDescription(tree)
|
||||
.setMessage(
|
||||
"""
|
||||
Not all parameters of this request mapping method are annotated; this may be a \
|
||||
mistake. If the unannotated parameters represent query string parameters, annotate \
|
||||
them with `@RequestParam`.""")
|
||||
"Not all parameters of this request mapping method are annotated; this may be a mistake. "
|
||||
+ "If the unannotated parameters represent query string parameters, annotate them with `@RequestParam`.")
|
||||
.build()
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
@@ -27,21 +27,17 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import javax.inject.Inject;
|
||||
import tech.picnic.errorprone.utils.Flags;
|
||||
import tech.picnic.errorprone.bugpatterns.util.Flags;
|
||||
|
||||
/** A {@link BugChecker} that flags {@code @RequestParam} parameters with an unsupported type. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
By default, `@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` \
|
||||
subtypes""",
|
||||
"By default, `@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
|
||||
link = BUG_PATTERNS_BASE_URL + "RequestParamType",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */)
|
||||
public final class RequestParamType extends BugChecker implements VariableTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String SUPPORTED_CUSTOM_TYPES_FLAG = "RequestParamType:SupportedCustomTypes";
|
||||
@@ -58,8 +54,7 @@ public final class RequestParamType extends BugChecker implements VariableTreeMa
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
@Inject
|
||||
RequestParamType(ErrorProneFlags flags) {
|
||||
public RequestParamType(ErrorProneFlags flags) {
|
||||
hasUnsupportedRequestParamType = hasUnsupportedRequestParamType(flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.common.AnnotationMirrors;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags methods with Spring's {@code @Scheduled} annotation that lack New
|
||||
* Relic Agent's {@code @Trace(dispatcher = true)}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Scheduled operation must start a new New Relic transaction",
|
||||
link = BUG_PATTERNS_BASE_URL + "ScheduledTransactionTrace",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class ScheduledTransactionTrace extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TRACE_ANNOTATION_FQCN = "com.newrelic.api.agent.Trace";
|
||||
private static final Matcher<Tree> IS_SCHEDULED =
|
||||
hasAnnotation("org.springframework.scheduling.annotation.Scheduled");
|
||||
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
|
||||
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
|
||||
|
||||
/** Instantiates a new {@link ScheduledTransactionTrace} instance. */
|
||||
public ScheduledTransactionTrace() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|
||||
|| !IS_SCHEDULED.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableList<AnnotationTree> traceAnnotations =
|
||||
TRACE_ANNOTATION.multiMatchResult(tree, state).matchingNodes();
|
||||
if (traceAnnotations.isEmpty()) {
|
||||
/* This method completely lacks the `@Trace` annotation; add it. */
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.builder()
|
||||
.addImport(TRACE_ANNOTATION_FQCN)
|
||||
.prefixWith(tree, "@Trace(dispatcher = true)")
|
||||
.build());
|
||||
}
|
||||
|
||||
AnnotationTree traceAnnotation = Iterables.getOnlyElement(traceAnnotations);
|
||||
if (isCorrectAnnotation(traceAnnotation)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `@Trace` annotation is present but does not specify `dispatcher = true`. Add or update
|
||||
* the `dispatcher` annotation element.
|
||||
*/
|
||||
return describeMatch(
|
||||
traceAnnotation,
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
traceAnnotation, state, "dispatcher", ImmutableList.of("true"))
|
||||
.build());
|
||||
}
|
||||
|
||||
private static boolean isCorrectAnnotation(AnnotationTree traceAnnotation) {
|
||||
return Boolean.TRUE.equals(
|
||||
AnnotationMirrors.getAnnotationValue(
|
||||
ASTHelpers.getAnnotationMirror(traceAnnotation), "dispatcher")
|
||||
.getValue());
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -23,7 +23,7 @@ import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that flags SLF4J usages that are likely to be in error. */
|
||||
// XXX: The special-casing of Throwable applies only to SLF4J 1.6.0+; see
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
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;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -17,16 +18,16 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.NewArrayTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import java.util.Optional;
|
||||
import tech.picnic.errorprone.utils.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@code @RequestMapping} annotations that can be written more
|
||||
@@ -78,25 +79,31 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
}
|
||||
|
||||
private static Optional<String> extractUniqueMethod(ExpressionTree arg, VisitorState state) {
|
||||
if (!(arg instanceof AssignmentTree assignment)) {
|
||||
throw new VerifyException("Annotation attribute is not an assignment:" + arg.getKind());
|
||||
verify(
|
||||
arg.getKind() == Kind.ASSIGNMENT,
|
||||
"Annotation attribute is not an assignment: %s",
|
||||
arg.getKind());
|
||||
|
||||
ExpressionTree expr = ((AssignmentTree) arg).getExpression();
|
||||
if (expr.getKind() != Kind.NEW_ARRAY) {
|
||||
return Optional.of(extractMethod(expr, state));
|
||||
}
|
||||
|
||||
ExpressionTree expr = assignment.getExpression();
|
||||
return expr instanceof NewArrayTree newArray
|
||||
? Optional.of(newArray.getInitializers())
|
||||
.filter(args -> args.size() == 1)
|
||||
.map(args -> extractMethod(args.get(0), state))
|
||||
: Optional.of(extractMethod(expr, state));
|
||||
NewArrayTree newArray = (NewArrayTree) expr;
|
||||
return Optional.of(newArray.getInitializers())
|
||||
.filter(args -> args.size() == 1)
|
||||
.map(args -> extractMethod(args.get(0), state));
|
||||
}
|
||||
|
||||
// XXX: Use switch pattern matching once the targeted JDK supports this.
|
||||
private static String extractMethod(ExpressionTree expr, VisitorState state) {
|
||||
return switch (expr.getKind()) {
|
||||
case IDENTIFIER -> SourceCode.treeToString(expr, state);
|
||||
case MEMBER_SELECT -> ((MemberSelectTree) expr).getIdentifier().toString();
|
||||
default -> throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
};
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
return SourceCode.treeToString(expr, state);
|
||||
case MEMBER_SELECT:
|
||||
return ((MemberSelectTree) expr).getIdentifier().toString();
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
}
|
||||
|
||||
private static Fix replaceAnnotation(
|
||||
@@ -107,8 +114,9 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
.map(arg -> SourceCode.treeToString(arg, state))
|
||||
.collect(joining(", "));
|
||||
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
String annotation = SuggestedFixes.qualifyType(state, fix, ANN_PACKAGE_PREFIX + newAnnotation);
|
||||
return fix.replace(tree, String.format("@%s(%s)", annotation, newArguments)).build();
|
||||
return SuggestedFix.builder()
|
||||
.addImport(ANN_PACKAGE_PREFIX + newAnnotation)
|
||||
.replace(tree, String.format("@%s(%s)", newAnnotation, newArguments))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,32 +2,14 @@ package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS;
|
||||
import static tech.picnic.errorprone.bugpatterns.NonStaticImport.NON_STATIC_IMPORT_CANDIDATE_MEMBERS;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.base.Verify;
|
||||
import com.google.common.collect.Comparators;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableRangeSet;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.ImmutableSortedMultiset;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.collect.MoreCollectors;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -38,76 +20,55 @@ import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matchers;
|
||||
import com.google.errorprone.predicates.TypePredicates;
|
||||
import com.google.errorprone.refaster.ImportPolicy;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** A {@link BugChecker} that flags type members that can and should be statically imported. */
|
||||
// XXX: This check is closely linked to `NonStaticImport`. Consider merging the two.
|
||||
/**
|
||||
* A {@link BugChecker} that flags methods and constants that can and should be statically imported.
|
||||
*/
|
||||
// XXX: Tricky cases:
|
||||
// - `org.springframework.http.HttpStatus` (not always an improvement, and `valueOf` must
|
||||
// certainly be excluded)
|
||||
// - `com.google.common.collect.Tables`
|
||||
// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN}`
|
||||
// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN"}`
|
||||
// XXX: Also introduce a check that disallows static imports of certain methods. Candidates:
|
||||
// - `com.google.common.base.Strings`
|
||||
// - `java.util.Optional.empty`
|
||||
// - `java.util.Locale.ROOT`
|
||||
// - `ZoneOffset.ofHours` and other `ofXXX`-style methods.
|
||||
// - `java.time.Clock`.
|
||||
// - Several other `java.time` classes.
|
||||
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Identifier should be statically imported",
|
||||
link = BUG_PATTERNS_BASE_URL + "StaticImport",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
tags = SIMPLIFICATION)
|
||||
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Types whose members should be statically imported, unless exempted by {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS} or {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}.
|
||||
*
|
||||
* <p>Types listed here should be mutually exclusive with {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_TYPES}.
|
||||
* #STATIC_IMPORT_EXEMPTED_MEMBERS} or {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_TYPES =
|
||||
ImmutableSet.of(
|
||||
BugPattern.LinkType.class.getCanonicalName(),
|
||||
BugPattern.SeverityLevel.class.getCanonicalName(),
|
||||
BugPattern.StandardTags.class.getCanonicalName(),
|
||||
Collections.class.getCanonicalName(),
|
||||
Collectors.class.getCanonicalName(),
|
||||
Comparator.class.getCanonicalName(),
|
||||
ImportPolicy.class.getCanonicalName(),
|
||||
Map.Entry.class.getCanonicalName(),
|
||||
Matchers.class.getCanonicalName(),
|
||||
MoreCollectors.class.getCanonicalName(),
|
||||
Pattern.class.getCanonicalName(),
|
||||
Preconditions.class.getCanonicalName(),
|
||||
Predicates.class.getCanonicalName(),
|
||||
StandardCharsets.class.getCanonicalName(),
|
||||
TypePredicates.class.getCanonicalName(),
|
||||
Verify.class.getCanonicalName(),
|
||||
"com.fasterxml.jackson.annotation.JsonCreator.Mode",
|
||||
"com.fasterxml.jackson.annotation.JsonFormat.Shape",
|
||||
"com.fasterxml.jackson.annotation.JsonInclude.Include",
|
||||
"com.fasterxml.jackson.annotation.JsonProperty.Access",
|
||||
"com.google.common.base.Preconditions",
|
||||
"com.google.common.base.Predicates",
|
||||
"com.google.common.base.Verify",
|
||||
"com.google.common.collect.MoreCollectors",
|
||||
"com.google.errorprone.BugPattern.LinkType",
|
||||
"com.google.errorprone.BugPattern.SeverityLevel",
|
||||
"com.google.errorprone.BugPattern.StandardTags",
|
||||
"com.google.errorprone.matchers.Matchers",
|
||||
"com.google.errorprone.refaster.ImportPolicy",
|
||||
"com.mongodb.client.model.Accumulators",
|
||||
"com.mongodb.client.model.Aggregates",
|
||||
"com.mongodb.client.model.Filters",
|
||||
@@ -115,6 +76,12 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"com.mongodb.client.model.Projections",
|
||||
"com.mongodb.client.model.Sorts",
|
||||
"com.mongodb.client.model.Updates",
|
||||
"java.nio.charset.StandardCharsets",
|
||||
"java.util.Collections",
|
||||
"java.util.Comparator",
|
||||
"java.util.Map.Entry",
|
||||
"java.util.regex.Pattern",
|
||||
"java.util.stream.Collectors",
|
||||
"org.assertj.core.api.Assertions",
|
||||
"org.assertj.core.api.InstanceOfAssertFactories",
|
||||
"org.assertj.core.api.SoftAssertions",
|
||||
@@ -135,59 +102,95 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"org.springframework.http.MediaType",
|
||||
"org.testng.Assert",
|
||||
"reactor.function.TupleUtils",
|
||||
"tech.picnic.errorprone.utils.MoreTypes");
|
||||
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
|
||||
|
||||
/**
|
||||
* Type members that should be statically imported.
|
||||
*
|
||||
* <p>Please note that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Types listed by {@link #STATIC_IMPORT_CANDIDATE_TYPES} should be omitted from this
|
||||
* collection.
|
||||
* <li>This collection should be mutually exclusive with {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_MEMBERS}.
|
||||
* <li>This collection should not list members contained in {@link
|
||||
* NonStaticImport#NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS}.
|
||||
* </ul>
|
||||
*/
|
||||
/** Type members that should be statically imported. */
|
||||
@VisibleForTesting
|
||||
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.putAll(Comparators.class.getCanonicalName(), "emptiesFirst", "emptiesLast")
|
||||
.put(Function.class.getCanonicalName(), "identity")
|
||||
.put(Functions.class.getCanonicalName(), "identity")
|
||||
.put(ImmutableList.class.getCanonicalName(), "toImmutableList")
|
||||
.putAll(
|
||||
ImmutableListMultimap.class.getCanonicalName(),
|
||||
"com.google.common.collect.ImmutableListMultimap",
|
||||
"flatteningToImmutableListMultimap",
|
||||
"toImmutableListMultimap")
|
||||
.put(ImmutableMap.class.getCanonicalName(), "toImmutableMap")
|
||||
.put(ImmutableMultiset.class.getCanonicalName(), "toImmutableMultiset")
|
||||
.put(ImmutableRangeSet.class.getCanonicalName(), "toImmutableRangeSet")
|
||||
.put(ImmutableSet.class.getCanonicalName(), "toImmutableSet")
|
||||
.put("com.google.common.collect.ImmutableList", "toImmutableList")
|
||||
.put("com.google.common.collect.ImmutableMap", "toImmutableMap")
|
||||
.put("com.google.common.collect.ImmutableMultiset", "toImmutableMultiset")
|
||||
.put("com.google.common.collect.ImmutableRangeSet", "toImmutableRangeSet")
|
||||
.putAll(
|
||||
ImmutableSetMultimap.class.getCanonicalName(),
|
||||
"com.google.common.collect.ImmutableSetMultimap",
|
||||
"flatteningToImmutableSetMultimap",
|
||||
"toImmutableSetMultimap")
|
||||
.put(ImmutableSortedMap.class.getCanonicalName(), "toImmutableSortedMap")
|
||||
.put(ImmutableSortedMultiset.class.getCanonicalName(), "toImmutableSortedMultiset")
|
||||
.put(ImmutableSortedSet.class.getCanonicalName(), "toImmutableSortedSet")
|
||||
.put(ImmutableTable.class.getCanonicalName(), "toImmutableTable")
|
||||
.put("com.google.common.collect.ImmutableSet", "toImmutableSet")
|
||||
.put("com.google.common.collect.ImmutableSortedMap", "toImmutableSortedMap")
|
||||
.put("com.google.common.collect.ImmutableSortedMultiset", "toImmutableSortedMultiset")
|
||||
.put("com.google.common.collect.ImmutableSortedSet", "toImmutableSortedSet")
|
||||
.put("com.google.common.collect.ImmutableTable", "toImmutableTable")
|
||||
.put("com.google.common.collect.Sets", "toImmutableEnumSet")
|
||||
.put("com.google.common.base.Functions", "identity")
|
||||
.put("java.time.ZoneOffset", "UTC")
|
||||
.put("java.util.function.Function", "identity")
|
||||
.put("java.util.function.Predicate", "not")
|
||||
.put("java.util.UUID", "randomUUID")
|
||||
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
|
||||
.putAll(
|
||||
Objects.class.getCanonicalName(),
|
||||
"java.util.Objects",
|
||||
"checkIndex",
|
||||
"checkFromIndexSize",
|
||||
"checkFromToIndex",
|
||||
"requireNonNull",
|
||||
"requireNonNullElse",
|
||||
"requireNonNullElseGet")
|
||||
.put(Predicate.class.getCanonicalName(), "not")
|
||||
.put(Sets.class.getCanonicalName(), "toImmutableEnumSet")
|
||||
.put(UUID.class.getCanonicalName(), "randomUUID")
|
||||
.put(ZoneOffset.class.getCanonicalName(), "UTC")
|
||||
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
|
||||
.putAll("com.google.common.collect.Comparators", "emptiesFirst", "emptiesLast")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Type members that should never be statically imported.
|
||||
*
|
||||
* <p>Identifiers listed by {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS} should be omitted from
|
||||
* this collection.
|
||||
*/
|
||||
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
|
||||
// method name that could be considered "too vague" or could conceivably mean something else in a
|
||||
// specific context is left out.
|
||||
@VisibleForTesting
|
||||
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
|
||||
ImmutableSetMultimap.<String, String>builder()
|
||||
.put("com.mongodb.client.model.Filters", "empty")
|
||||
.putAll(
|
||||
"java.util.Collections",
|
||||
"addAll",
|
||||
"copy",
|
||||
"fill",
|
||||
"list",
|
||||
"max",
|
||||
"min",
|
||||
"nCopies",
|
||||
"rotate",
|
||||
"sort",
|
||||
"swap")
|
||||
.putAll("java.util.regex.Pattern", "compile", "matches", "quote")
|
||||
.put("org.springframework.http.MediaType", "ALL")
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Identifiers that should never be statically imported.
|
||||
*
|
||||
* <p>This should be a superset of the identifiers flagged by {@link
|
||||
* com.google.errorprone.bugpatterns.BadImport}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final ImmutableSet<String> STATIC_IMPORT_EXEMPTED_IDENTIFIERS =
|
||||
ImmutableSet.of(
|
||||
"builder",
|
||||
"create",
|
||||
"copyOf",
|
||||
"from",
|
||||
"getDefaultInstance",
|
||||
"INSTANCE",
|
||||
"newBuilder",
|
||||
"of",
|
||||
"valueOf");
|
||||
|
||||
/** Instantiates a new {@link StaticImport} instance. */
|
||||
public StaticImport() {}
|
||||
|
||||
@@ -212,21 +215,26 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
Tree parentTree =
|
||||
requireNonNull(state.getPath().getParentPath(), "MemberSelectTree lacks enclosing node")
|
||||
.getLeaf();
|
||||
|
||||
return parentTree instanceof MethodInvocationTree methodInvocation
|
||||
? methodInvocation.getTypeArguments().isEmpty()
|
||||
: (parentTree.getKind() != Kind.IMPORT && parentTree.getKind() != Kind.MEMBER_SELECT);
|
||||
switch (parentTree.getKind()) {
|
||||
case IMPORT:
|
||||
case MEMBER_SELECT:
|
||||
return false;
|
||||
case METHOD_INVOCATION:
|
||||
return ((MethodInvocationTree) parentTree).getTypeArguments().isEmpty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCandidate(MemberSelectTree tree) {
|
||||
String identifier = tree.getIdentifier().toString();
|
||||
if (NON_STATIC_IMPORT_CANDIDATE_IDENTIFIERS.contains(identifier)) {
|
||||
if (STATIC_IMPORT_EXEMPTED_IDENTIFIERS.contains(identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Type type = ASTHelpers.getType(tree.getExpression());
|
||||
return type != null
|
||||
&& !NON_STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(type.toString(), identifier);
|
||||
&& !STATIC_IMPORT_EXEMPTED_MEMBERS.containsEntry(type.toString(), identifier);
|
||||
}
|
||||
|
||||
private static Optional<String> getCandidateSimpleName(StaticImportInfo importInfo) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.RPAREN;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.ErrorProneTokens;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags calls to {@link String#toLowerCase()} and {@link
|
||||
* String#toUpperCase()}, as these methods implicitly rely on the environment's default locale.
|
||||
*/
|
||||
// XXX: Also flag `String::toLowerCase` and `String::toUpperCase` method references. For these cases
|
||||
// the suggested fix should introduce a lambda expression with a parameter of which the name does
|
||||
// not coincide with the name of an existing variable name. Such functionality should likely be
|
||||
// introduced in a utility class.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Specify a `Locale` when calling `String#to{Lower,Upper}Case`",
|
||||
link = BUG_PATTERNS_BASE_URL + "StringCaseLocaleUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class StringCaseLocaleUsage extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> DEFAULT_LOCALE_CASE_CONVERSION =
|
||||
instanceMethod()
|
||||
.onExactClass(String.class.getName())
|
||||
.namedAnyOf("toLowerCase", "toUpperCase")
|
||||
.withNoParameters();
|
||||
|
||||
/** Instantiates a new {@link StringCaseLocaleUsage} instance. */
|
||||
public StringCaseLocaleUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!DEFAULT_LOCALE_CASE_CONVERSION.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
int closingParenPosition = getClosingParenPosition(tree, state);
|
||||
if (closingParenPosition == Position.NOPOS) {
|
||||
return describeMatch(tree);
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(suggestLocale(closingParenPosition, "Locale.ROOT"))
|
||||
.addFix(suggestLocale(closingParenPosition, "Locale.getDefault()"))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Fix suggestLocale(int insertPosition, String locale) {
|
||||
return SuggestedFix.builder()
|
||||
.addImport("java.util.Locale")
|
||||
.replace(insertPosition, insertPosition, locale)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static int getClosingParenPosition(MethodInvocationTree tree, VisitorState state) {
|
||||
int startPosition = ASTHelpers.getStartPosition(tree);
|
||||
if (startPosition == Position.NOPOS) {
|
||||
return Position.NOPOS;
|
||||
}
|
||||
|
||||
return Streams.findLast(
|
||||
ErrorProneTokens.getTokens(SourceCode.treeToString(tree, state), state.context).stream()
|
||||
.filter(t -> t.kind() == RPAREN))
|
||||
.map(token -> startPosition + token.pos())
|
||||
.orElse(Position.NOPOS);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +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.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -23,12 +23,12 @@ 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 com.sun.tools.javac.util.Convert;
|
||||
import java.util.Formattable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link String#format(String, Object...)} invocations which can be
|
||||
@@ -49,7 +49,7 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Splitter FORMAT_SPECIFIER_SPLITTER = Splitter.on("%s");
|
||||
private static final Matcher<ExpressionTree> STRING_FORMAT_INVOCATION =
|
||||
staticMethod().onClass(String.class.getCanonicalName()).named("format");
|
||||
staticMethod().onClass(String.class.getName()).named("format");
|
||||
private static final Supplier<Type> CHAR_SEQUENCE_TYPE =
|
||||
Suppliers.typeFromClass(CharSequence.class);
|
||||
private static final Supplier<Type> FORMATTABLE_TYPE = Suppliers.typeFromClass(Formattable.class);
|
||||
@@ -150,7 +150,7 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(tree.getMethodSelect(), "String.join")
|
||||
.replace(arguments.next(), Constants.format(separator));
|
||||
.replace(arguments.next(), String.format("\"%s\"", Convert.quote(separator)));
|
||||
|
||||
while (arguments.hasNext()) {
|
||||
ExpressionTree argument = arguments.next();
|
||||
|
||||
@@ -10,7 +10,7 @@ import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -34,9 +34,7 @@ import java.time.ZonedDateTime;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"""
|
||||
Derive the current time from an existing `Clock` Spring bean, and don't rely on a \
|
||||
`Clock`'s time zone""",
|
||||
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone",
|
||||
link = BUG_PATTERNS_BASE_URL + "TimeZoneUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
@@ -47,11 +45,11 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
anyOf(
|
||||
allOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf(Clock.class.getCanonicalName())
|
||||
.onDescendantOf(Clock.class.getName())
|
||||
.namedAnyOf("getZone", "withZone"),
|
||||
not(enclosingClass(isSubtypeOf(Clock.class)))),
|
||||
staticMethod()
|
||||
.onClass(Clock.class.getCanonicalName())
|
||||
.onClass(Clock.class.getName())
|
||||
.namedAnyOf(
|
||||
"system",
|
||||
"systemDefaultZone",
|
||||
@@ -61,17 +59,14 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
"tickSeconds"),
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
LocalDate.class.getCanonicalName(),
|
||||
LocalDateTime.class.getCanonicalName(),
|
||||
LocalTime.class.getCanonicalName(),
|
||||
OffsetDateTime.class.getCanonicalName(),
|
||||
OffsetTime.class.getCanonicalName(),
|
||||
ZonedDateTime.class.getCanonicalName())
|
||||
LocalDate.class.getName(),
|
||||
LocalDateTime.class.getName(),
|
||||
LocalTime.class.getName(),
|
||||
OffsetDateTime.class.getName(),
|
||||
OffsetTime.class.getName(),
|
||||
ZonedDateTime.class.getName())
|
||||
.named("now"),
|
||||
staticMethod()
|
||||
.onClassAny(Instant.class.getCanonicalName())
|
||||
.named("now")
|
||||
.withNoParameters());
|
||||
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
|
||||
|
||||
/** Instantiates a new {@link TimeZoneUsage} instance. */
|
||||
public TimeZoneUsage() {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
@@ -9,6 +9,7 @@ import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
@@ -102,7 +103,7 @@ public final class AnnotationAttributeMatcher implements Serializable {
|
||||
* @param tree The annotation AST node to be inspected.
|
||||
* @return Any matching annotation arguments.
|
||||
*/
|
||||
public Stream<ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
|
||||
public Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
|
||||
Type type = ASTHelpers.getType(tree.getAnnotationType());
|
||||
if (type == null) {
|
||||
return Stream.empty();
|
||||
@@ -110,13 +111,12 @@ public final class AnnotationAttributeMatcher implements Serializable {
|
||||
|
||||
String annotationType = type.toString();
|
||||
return tree.getArguments().stream()
|
||||
.map(ExpressionTree.class::cast)
|
||||
.filter(a -> matches(annotationType, extractAttributeName(a)));
|
||||
}
|
||||
|
||||
private static String extractAttributeName(ExpressionTree expr) {
|
||||
return (expr instanceof AssignmentTree assignment)
|
||||
? ASTHelpers.getSymbol(assignment.getVariable()).getSimpleName().toString()
|
||||
return (expr.getKind() == Kind.ASSIGNMENT)
|
||||
? ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable()).getSimpleName().toString()
|
||||
: "value";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static tech.picnic.errorprone.utils.JavaKeywords.isValidIdentifier;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
/** Utility class providing documentation-related code. */
|
||||
public final class Documentation {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
@@ -16,7 +16,10 @@ public final class Flags {
|
||||
* provided, or if the flag's value is the empty string.
|
||||
*/
|
||||
public static ImmutableList<String> getList(ErrorProneFlags errorProneFlags, String name) {
|
||||
ImmutableList<String> list = errorProneFlags.getListOrEmpty(name);
|
||||
return list.equals(ImmutableList.of("")) ? ImmutableList.of() : list;
|
||||
return errorProneFlags
|
||||
.getList(name)
|
||||
.map(ImmutableList::copyOf)
|
||||
.filter(flags -> !flags.equals(ImmutableList.of("")))
|
||||
.orElseGet(ImmutableList::of);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
@@ -17,7 +17,6 @@ public final class JavaKeywords {
|
||||
*/
|
||||
private static final ImmutableSet<String> BOOLEAN_AND_NULL_LITERALS =
|
||||
ImmutableSet.of("true", "false", "null");
|
||||
|
||||
/**
|
||||
* List of all reserved keywords in the Java language.
|
||||
*
|
||||
@@ -77,7 +76,6 @@ public final class JavaKeywords {
|
||||
"void",
|
||||
"volatile",
|
||||
"while");
|
||||
|
||||
/**
|
||||
* List of all contextual keywords in the Java language.
|
||||
*
|
||||
@@ -102,7 +100,6 @@ public final class JavaKeywords {
|
||||
"var",
|
||||
"with",
|
||||
"yield");
|
||||
|
||||
/** List of all keywords in the Java language. */
|
||||
private static final ImmutableSet<String> ALL_KEYWORDS =
|
||||
Sets.union(RESERVED_KEYWORDS, CONTEXTUAL_KEYWORDS).immutableCopy();
|
||||
@@ -117,7 +114,6 @@ public final class JavaKeywords {
|
||||
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.8">JDK 17 JLS
|
||||
* section 3.8: Identifiers</a>
|
||||
*/
|
||||
@SuppressWarnings("java:S1067" /* Chaining conjunctions like this does not impact readability. */)
|
||||
public static boolean isValidIdentifier(String str) {
|
||||
return !str.isEmpty()
|
||||
&& !isReservedKeyword(str)
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
@@ -18,10 +18,6 @@ import java.util.regex.Pattern;
|
||||
public final class MethodMatcherFactory {
|
||||
private static final Splitter ARGUMENT_TYPE_SPLITTER =
|
||||
Splitter.on(',').trimResults().omitEmptyStrings();
|
||||
|
||||
// XXX: Check whether we can use a parser for "standard" Java signatures here. Maybe
|
||||
// `sun.reflect.generics.parser.SignatureParser`?
|
||||
@SuppressWarnings("java:S5998" /* In practice there will be only modest recursion. */)
|
||||
private static final Pattern METHOD_SIGNATURE =
|
||||
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A collection of helper methods for working with the AST.
|
||||
*
|
||||
* <p>These methods are additions to the ones found in {@link ASTHelpers}.
|
||||
* <p>These methods are additions to the ones found in {@link
|
||||
* com.google.errorprone.util.ASTHelpers}.
|
||||
*/
|
||||
public final class MoreASTHelpers {
|
||||
private MoreASTHelpers() {}
|
||||
@@ -49,33 +46,4 @@ public final class MoreASTHelpers {
|
||||
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
|
||||
return !findMethods(methodName, state).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodTree} from which control flow would exit if there would be a {@code
|
||||
* return} statement at the given {@link VisitorState}'s current {@link VisitorState#getPath()
|
||||
* path}.
|
||||
*
|
||||
* @param state The {@link VisitorState} from which to derive the AST location of interest.
|
||||
* @return A {@link MethodTree}, unless the {@link VisitorState}'s path does not point to an AST
|
||||
* node located inside a method, or if the (hypothetical) {@code return} statement would exit
|
||||
* a lambda expression instead.
|
||||
*/
|
||||
public static Optional<MethodTree> findMethodExitedOnReturn(VisitorState state) {
|
||||
return Optional.ofNullable(state.findEnclosing(LambdaExpressionTree.class, MethodTree.class))
|
||||
.filter(MethodTree.class::isInstance)
|
||||
.map(MethodTree.class::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given trees are of the same type, after type erasure.
|
||||
*
|
||||
* @param treeA The first tree of interest.
|
||||
* @param treeB The second tree of interest.
|
||||
* @param state The {@link VisitorState} describing the context in which the given trees were
|
||||
* found.
|
||||
* @return Whether the specified trees have the same erased types.
|
||||
*/
|
||||
public static boolean areSameType(Tree treeA, Tree treeB, VisitorState state) {
|
||||
return ASTHelpers.isSameType(ASTHelpers.getType(treeA), ASTHelpers.getType(treeB), state);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static tech.picnic.errorprone.utils.MoreMatchers.hasMetaAnnotation;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.matchers.AnnotationMatcherUtils;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
@@ -34,7 +34,6 @@ public final class MoreJUnitMatchers {
|
||||
anyOf(
|
||||
isType("org.junit.jupiter.api.Test"),
|
||||
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
|
||||
|
||||
/** Matches JUnit Jupiter setup and teardown methods. */
|
||||
public static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
|
||||
annotations(
|
||||
@@ -44,7 +43,6 @@ public final class MoreJUnitMatchers {
|
||||
isType("org.junit.jupiter.api.AfterEach"),
|
||||
isType("org.junit.jupiter.api.BeforeAll"),
|
||||
isType("org.junit.jupiter.api.BeforeEach")));
|
||||
|
||||
/**
|
||||
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation.
|
||||
*/
|
||||
@@ -57,58 +55,25 @@ public final class MoreJUnitMatchers {
|
||||
* Returns the names of the JUnit value factory methods specified by the given {@link
|
||||
* org.junit.jupiter.params.provider.MethodSource} annotation.
|
||||
*
|
||||
* <p>This method differs from {@link #getMethodSourceFactoryDescriptors(AnnotationTree,
|
||||
* MethodTree)} in that it drops any parenthesized method parameter type enumerations. That is,
|
||||
* method descriptors such as {@code factoryMethod()} and {@code factoryMethod(java.lang.String)}
|
||||
* are both simplified to just {@code factoryMethod}. This also means that the returned method
|
||||
* names may not unambiguously reference a single value factory method; in such a case JUnit will
|
||||
* throw an error at runtime.
|
||||
*
|
||||
* @param methodSourceAnnotation The annotation from which to extract value factory method names.
|
||||
* @param method The method on which the annotation is located.
|
||||
* @return One or more value factory descriptors, in the order defined.
|
||||
* @see #getMethodSourceFactoryDescriptors(AnnotationTree, MethodTree)
|
||||
* @return One or more value factory names.
|
||||
*/
|
||||
// XXX: Drop this method in favour of `#getMethodSourceFactoryDescriptors`. That will require
|
||||
// callers to either explicitly drop information, or perform a more advanced analysis.
|
||||
public static ImmutableList<String> getMethodSourceFactoryNames(
|
||||
AnnotationTree methodSourceAnnotation, MethodTree method) {
|
||||
return getMethodSourceFactoryDescriptors(methodSourceAnnotation, method).stream()
|
||||
.map(
|
||||
descriptor -> {
|
||||
int index = descriptor.indexOf('(');
|
||||
return index < 0 ? descriptor : descriptor.substring(0, index);
|
||||
})
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the descriptors of the JUnit value factory methods specified by the given {@link
|
||||
* org.junit.jupiter.params.provider.MethodSource} annotation.
|
||||
*
|
||||
* @param methodSourceAnnotation The annotation from which to extract value factory method
|
||||
* descriptors.
|
||||
* @param method The method on which the annotation is located.
|
||||
* @return One or more value factory descriptors, in the order defined.
|
||||
* @see #getMethodSourceFactoryNames(AnnotationTree, MethodTree)
|
||||
*/
|
||||
// XXX: Rather than strings, have this method return instances of a value type capable of
|
||||
// resolving the value factory method pointed to.
|
||||
public static ImmutableList<String> getMethodSourceFactoryDescriptors(
|
||||
static ImmutableSet<String> getMethodSourceFactoryNames(
|
||||
AnnotationTree methodSourceAnnotation, MethodTree method) {
|
||||
String methodName = method.getName().toString();
|
||||
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value");
|
||||
|
||||
if (!(value instanceof NewArrayTree newArray)) {
|
||||
return ImmutableList.of(toMethodSourceFactoryDescriptor(value, methodName));
|
||||
if (!(value instanceof NewArrayTree)) {
|
||||
return ImmutableSet.of(toMethodSourceFactoryName(value, methodName));
|
||||
}
|
||||
|
||||
return newArray.getInitializers().stream()
|
||||
.map(name -> toMethodSourceFactoryDescriptor(name, methodName))
|
||||
.collect(toImmutableList());
|
||||
return ((NewArrayTree) value)
|
||||
.getInitializers().stream()
|
||||
.map(name -> toMethodSourceFactoryName(name, methodName))
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
private static String toMethodSourceFactoryDescriptor(
|
||||
private static String toMethodSourceFactoryName(
|
||||
@Nullable ExpressionTree tree, String annotatedMethodName) {
|
||||
return requireNonNullElse(
|
||||
Strings.emptyToNull(ASTHelpers.constValue(tree, String.class)), annotatedMethodName);
|
||||
@@ -0,0 +1,39 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.Matchers;
|
||||
import com.google.errorprone.predicates.TypePredicate;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
|
||||
/**
|
||||
* A collection of general-purpose {@link Matcher}s.
|
||||
*
|
||||
* <p>These methods are additions to the ones found in {@link Matchers}.
|
||||
*/
|
||||
public final class MoreMatchers {
|
||||
private MoreMatchers() {}
|
||||
|
||||
/**
|
||||
* Returns a {@link Matcher} that determines whether a given {@link AnnotationTree} has a
|
||||
* meta-annotation of the specified type.
|
||||
*
|
||||
* @param <T> The type of tree to match against.
|
||||
* @param annotationType The binary type name of the annotation (e.g.
|
||||
* "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName")
|
||||
* @return A {@link Matcher} that matches trees with the specified meta-annotation.
|
||||
*/
|
||||
public static <T extends AnnotationTree> Matcher<T> hasMetaAnnotation(String annotationType) {
|
||||
TypePredicate typePredicate = hasAnnotation(annotationType);
|
||||
return (tree, state) -> {
|
||||
Symbol sym = ASTHelpers.getSymbol(tree);
|
||||
return sym != null && typePredicate.apply(sym.type, state);
|
||||
};
|
||||
}
|
||||
|
||||
// XXX: Consider moving to a `MoreTypePredicates` utility class.
|
||||
private static TypePredicate hasAnnotation(String annotationClassName) {
|
||||
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.sun.tools.javac.util.Position.NOPOS;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
||||
|
||||
/**
|
||||
* A collection of Error Prone utility methods for dealing with the source code representation of
|
||||
* AST nodes.
|
||||
*/
|
||||
public final class SourceCode {
|
||||
/** The complement of {@link CharMatcher#whitespace()}. */
|
||||
private static final CharMatcher NON_WHITESPACE_MATCHER = CharMatcher.whitespace().negate();
|
||||
|
||||
private SourceCode() {}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the given {@link Tree}, preferring the original source code
|
||||
* (if available) over its prettified representation.
|
||||
*
|
||||
* @param tree The AST node of interest.
|
||||
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
|
||||
* found.
|
||||
* @return A non-{@code null} string.
|
||||
*/
|
||||
public static String treeToString(Tree tree, VisitorState state) {
|
||||
String src = state.getSourceForNode(tree);
|
||||
return src != null ? src : tree.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SuggestedFix} for the deletion of the given {@link Tree}, including any
|
||||
* whitespace that follows it.
|
||||
*
|
||||
* <p>Removing trailing whitespace may prevent the introduction of an empty line at the start of a
|
||||
* code block; such empty lines are not removed when formatting the code using Google Java Format.
|
||||
*
|
||||
* @param tree The AST node of interest.
|
||||
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
|
||||
* found.
|
||||
* @return A non-{@code null} {@link SuggestedFix} similar to one produced by {@link
|
||||
* SuggestedFix#delete(Tree)}.
|
||||
*/
|
||||
public static SuggestedFix deleteWithTrailingWhitespace(Tree tree, VisitorState state) {
|
||||
CharSequence sourceCode = state.getSourceCode();
|
||||
int endPos = state.getEndPosition(tree);
|
||||
if (sourceCode == null || endPos == NOPOS) {
|
||||
/* We can't identify the trailing whitespace; delete just the tree. */
|
||||
return SuggestedFix.delete(tree);
|
||||
}
|
||||
|
||||
int whitespaceEndPos = NON_WHITESPACE_MATCHER.indexIn(sourceCode, endPos);
|
||||
return SuggestedFix.replace(
|
||||
((DiagnosticPosition) tree).getStartPosition(),
|
||||
whitespaceEndPos == -1 ? sourceCode.length() : whitespaceEndPos,
|
||||
"");
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,11 @@
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.sun.tools.javac.code.ClassFinder;
|
||||
import com.sun.tools.javac.code.Source;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||
import com.sun.tools.javac.code.Symbol.ModuleSymbol;
|
||||
import com.sun.tools.javac.code.Symtab;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/**
|
||||
* Utility class that helps decide whether it is appropriate to introduce references to (well-known)
|
||||
@@ -35,7 +28,14 @@ public enum ThirdPartyLibrary {
|
||||
*
|
||||
* @see <a href="https://github.com/google/guava">Guava on GitHub</a>
|
||||
*/
|
||||
GUAVA(ImmutableList.class.getCanonicalName()),
|
||||
GUAVA("com.google.common.collect.ImmutableList"),
|
||||
/**
|
||||
* New Relic's Java agent API.
|
||||
*
|
||||
* @see <a href="https://github.com/newrelic/newrelic-java-agent/tree/main/newrelic-api">New Relic
|
||||
* Java agent API on GitHub</a>
|
||||
*/
|
||||
NEW_RELIC_AGENT_API("com.newrelic.api.agent.Agent"),
|
||||
/**
|
||||
* VMWare's Project Reactor.
|
||||
*
|
||||
@@ -70,15 +70,8 @@ public enum ThirdPartyLibrary {
|
||||
return canUse.get(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given fully qualified type is available on the current class path.
|
||||
*
|
||||
* @param typeName The type of interest.
|
||||
* @param state The context under consideration.
|
||||
* @return {@code true} iff it is okay to assume or create a dependency on this type.
|
||||
*/
|
||||
public static boolean canIntroduceUsage(String typeName, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(typeName, state);
|
||||
private static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,38 +80,21 @@ public enum ThirdPartyLibrary {
|
||||
* <p>The {@link VisitorState}'s symbol table is consulted first. If the type has not yet been
|
||||
* loaded, then an attempt is made to do so.
|
||||
*/
|
||||
private static boolean isKnownClass(String typeName, VisitorState state) {
|
||||
return isPublicClassInSymbolTable(typeName, state) || canLoadPublicClass(typeName, state);
|
||||
private static boolean isKnownClass(String className, VisitorState state) {
|
||||
return state.getTypeFromString(className) != null || canLoadClass(className, state);
|
||||
}
|
||||
|
||||
private static boolean isPublicClassInSymbolTable(String typeName, VisitorState state) {
|
||||
Type type = state.getTypeFromString(typeName);
|
||||
return type != null && isPublic(type.tsym);
|
||||
}
|
||||
|
||||
private static boolean canLoadPublicClass(String typeName, VisitorState state) {
|
||||
private static boolean canLoadClass(String className, VisitorState state) {
|
||||
ClassFinder classFinder = ClassFinder.instance(state.context);
|
||||
Symtab symtab = state.getSymtab();
|
||||
// XXX: Drop support for targeting Java 8 once the oldest supported JDK drops such support.
|
||||
ModuleSymbol module =
|
||||
Source.instance(state.context).compareTo(Source.JDK9) < 0
|
||||
? symtab.noModule
|
||||
: symtab.unnamedModule;
|
||||
Name binaryName = state.binaryNameFromClassname(typeName);
|
||||
Name binaryName = state.binaryNameFromClassname(className);
|
||||
try {
|
||||
return isPublic(classFinder.loadClass(module, binaryName));
|
||||
} catch (
|
||||
@SuppressWarnings("java:S1166" /* Not exceptional. */)
|
||||
CompletionFailure e) {
|
||||
classFinder.loadClass(state.getSymtab().unnamedModule, binaryName);
|
||||
return true;
|
||||
} catch (CompletionFailure e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Once we target JDK 14+, drop this method in favour of `Symbol#isPublic()`.
|
||||
private static boolean isPublic(Symbol symbol) {
|
||||
return symbol.getModifiers().contains(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreClasspath(VisitorState state) {
|
||||
return state
|
||||
.errorProneOptions()
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Auxiliary utilities for use by Error Prone checks. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package tech.picnic.errorprone.utils;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
@@ -3,40 +3,77 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.ImmutableSortedMultiset;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Matches;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import org.assertj.core.api.AbstractCollectionAssert;
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import org.assertj.core.api.MapAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
|
||||
|
||||
@OnlineDocumentation
|
||||
final class AssertJMapRules {
|
||||
private AssertJMapRules() {}
|
||||
|
||||
// XXX: Reduce boilerplate using a `Matcher` that identifies "empty" instances.
|
||||
static final class AbstractMapAssertIsEmpty<K, V> {
|
||||
@BeforeTemplate
|
||||
void before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert,
|
||||
@Matches(IsEmpty.class) Map<? extends K, ? extends V> wellTypedMap,
|
||||
@Matches(IsEmpty.class) Map<?, ?> arbitrarilyTypedMap,
|
||||
@Matches(IsEmpty.class) Iterable<? extends K> keys) {
|
||||
@SuppressWarnings("unchecked")
|
||||
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
|
||||
Refaster.anyOf(
|
||||
mapAssert.containsExactlyEntriesOf(wellTypedMap),
|
||||
mapAssert.containsExactlyInAnyOrderEntriesOf(wellTypedMap),
|
||||
mapAssert.hasSameSizeAs(arbitrarilyTypedMap),
|
||||
mapAssert.isEqualTo(arbitrarilyTypedMap),
|
||||
mapAssert.containsOnlyKeys(keys),
|
||||
mapAssert.containsExactlyEntriesOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableMap.of(),
|
||||
ImmutableBiMap.of(),
|
||||
ImmutableSortedMap.of(),
|
||||
new HashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
new TreeMap<>())),
|
||||
mapAssert.hasSameSizeAs(
|
||||
Refaster.anyOf(
|
||||
ImmutableMap.of(),
|
||||
ImmutableBiMap.of(),
|
||||
ImmutableSortedMap.of(),
|
||||
new HashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
new TreeMap<>())),
|
||||
mapAssert.isEqualTo(
|
||||
Refaster.anyOf(
|
||||
ImmutableMap.of(),
|
||||
ImmutableBiMap.of(),
|
||||
ImmutableSortedMap.of(),
|
||||
new HashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
new TreeMap<>())),
|
||||
mapAssert.containsOnlyKeys(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
mapAssert.containsExactly(),
|
||||
mapAssert.containsOnly(),
|
||||
mapAssert.containsOnlyKeys());
|
||||
@@ -72,9 +109,15 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AbstractMapAssertIsNotEmpty<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractMapAssert<?, ?, K, V> before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, @Matches(IsEmpty.class) Map<?, ?> map) {
|
||||
return mapAssert.isNotEqualTo(map);
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
|
||||
return mapAssert.isNotEqualTo(
|
||||
Refaster.anyOf(
|
||||
ImmutableMap.of(),
|
||||
ImmutableBiMap.of(),
|
||||
ImmutableSortedMap.of(),
|
||||
new HashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
new TreeMap<>()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@@ -102,14 +145,12 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractMapAssert<?, ?, K, V> before(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
return mapAssert.isEqualTo(map);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
AbstractMapAssert<?, ?, K, V> after(
|
||||
AbstractMapAssert<?, ?, K, V> mapAssert, Map<? extends K, ? extends V> map) {
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
return mapAssert.containsExactlyInAnyOrderEntriesOf(map);
|
||||
}
|
||||
}
|
||||
@@ -143,12 +184,12 @@ final class AssertJMapRules {
|
||||
|
||||
static final class AbstractMapAssertHasSameSizeAs<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
|
||||
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
return mapAssert.hasSize(map.size());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<?, ?> map) {
|
||||
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
|
||||
return mapAssert.hasSameSizeAs(map);
|
||||
}
|
||||
}
|
||||
@@ -179,20 +220,6 @@ final class AssertJMapRules {
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatMapContainsOnlyKeys<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(
|
||||
Map<K, V> map, Set<? extends K> keys) {
|
||||
return assertThat(map.keySet()).hasSameElementsAs(keys);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
MapAssert<K, V> after(Map<K, V> map, Set<? extends K> keys) {
|
||||
return assertThat(map).containsOnlyKeys(keys);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatMapContainsValue<K, V> {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {
|
||||
|
||||
@@ -23,8 +23,6 @@ final class AssertJPrimitiveRules {
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"java:S1244" /* The (in)equality checks are fragile, but may be seen in the wild. */)
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual == expected).isTrue(), assertThat(actual != expected).isFalse());
|
||||
@@ -45,8 +43,6 @@ final class AssertJPrimitiveRules {
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"java:S1244" /* The (in)equality checks are fragile, but may be seen in the wild. */)
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual != expected).isTrue(), assertThat(actual == expected).isFalse());
|
||||
|
||||
@@ -6,23 +6,28 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMultiset;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Multiset;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Matches;
|
||||
import com.google.errorprone.refaster.annotation.NotMatches;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalDouble;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Stream;
|
||||
@@ -42,7 +47,6 @@ import org.assertj.core.api.OptionalIntAssert;
|
||||
import org.assertj.core.api.OptionalLongAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsArray;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
|
||||
|
||||
/** Refaster rules related to AssertJ expressions and statements. */
|
||||
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
|
||||
@@ -177,16 +181,63 @@ final class AssertJRules {
|
||||
static final class AssertThatObjectEnumerableIsEmpty<E> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
void before(
|
||||
ObjectEnumerableAssert<?, E> enumAssert,
|
||||
@Matches(IsEmpty.class) Iterable<? extends E> wellTypedIterable,
|
||||
@Matches(IsEmpty.class) Iterable<?> arbitrarilyTypedIterable) {
|
||||
void before(ObjectEnumerableAssert<?, E> enumAssert) {
|
||||
Refaster.anyOf(
|
||||
enumAssert.containsExactlyElementsOf(wellTypedIterable),
|
||||
enumAssert.containsExactlyInAnyOrderElementsOf(wellTypedIterable),
|
||||
enumAssert.hasSameElementsAs(wellTypedIterable),
|
||||
enumAssert.hasSameSizeAs(arbitrarilyTypedIterable),
|
||||
enumAssert.isSubsetOf(wellTypedIterable),
|
||||
enumAssert.containsExactlyElementsOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.containsExactlyInAnyOrderElementsOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.hasSameElementsAs(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.hasSameSizeAs(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.isSubsetOf(
|
||||
Refaster.anyOf(
|
||||
ImmutableList.of(),
|
||||
new ArrayList<>(),
|
||||
ImmutableSet.of(),
|
||||
new HashSet<>(),
|
||||
new LinkedHashSet<>(),
|
||||
ImmutableSortedSet.of(),
|
||||
new TreeSet<>(),
|
||||
ImmutableMultiset.of(),
|
||||
ImmutableSortedMultiset.of())),
|
||||
enumAssert.containsExactly(),
|
||||
enumAssert.containsExactlyInAnyOrder(),
|
||||
enumAssert.containsOnly(),
|
||||
@@ -530,13 +581,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsAnyElementsOf<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsAnyElementsOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsAnyElementsOf(iterable);
|
||||
}
|
||||
|
||||
@@ -551,13 +602,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsAnyOf<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsAnyOf(array);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsAnyOf(array);
|
||||
}
|
||||
|
||||
@@ -573,14 +624,14 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -596,13 +647,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsAll<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsAll(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsAll(iterable);
|
||||
}
|
||||
|
||||
@@ -617,13 +668,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContains<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).contains(array);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).contains(array);
|
||||
}
|
||||
|
||||
@@ -639,14 +690,14 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -661,7 +712,7 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsExactlyElementsOf<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsExactlyElementsOf(iterable);
|
||||
}
|
||||
|
||||
@@ -676,7 +727,7 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsExactly<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsExactly(array);
|
||||
}
|
||||
|
||||
@@ -692,7 +743,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsExactly" /* Varargs converted to array. */)
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsExactly(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -708,13 +759,13 @@ final class AssertJRules {
|
||||
S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsExactlyInAnyOrderElementsOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
AbstractCollectionAssert<?, ?, T, ?> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Multiset<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsExactlyInAnyOrderElementsOf(iterable);
|
||||
}
|
||||
|
||||
@@ -729,13 +780,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsExactlyInAnyOrder<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsExactlyInAnyOrder(array);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
AbstractCollectionAssert<?, ?, T, ?> before2(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends Multiset<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsExactlyInAnyOrder(array);
|
||||
}
|
||||
|
||||
@@ -751,7 +802,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector))
|
||||
.containsExactlyInAnyOrder(Refaster.asVarargs(elements));
|
||||
}
|
||||
@@ -759,7 +810,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
|
||||
AbstractCollectionAssert<?, ?, T, ?> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Multiset<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector))
|
||||
.containsExactlyInAnyOrder(Refaster.asVarargs(elements));
|
||||
}
|
||||
@@ -776,13 +827,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsSequence<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsSequence(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, U[] iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] iterable) {
|
||||
return assertThat(stream.collect(collector)).containsSequence(iterable);
|
||||
}
|
||||
|
||||
@@ -798,7 +849,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsSequence" /* Varargs converted to array. */)
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsSequence(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -814,13 +865,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsSubsequence<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).containsSubsequence(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, U[] iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] iterable) {
|
||||
return assertThat(stream.collect(collector)).containsSubsequence(iterable);
|
||||
}
|
||||
|
||||
@@ -836,7 +887,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsSubsequence" /* Varargs converted to array. */)
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector))
|
||||
.containsSubsequence(Refaster.asVarargs(elements));
|
||||
}
|
||||
@@ -853,13 +904,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamDoesNotContainAnyElementsOf<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).doesNotContainAnyElementsOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).doesNotContainAnyElementsOf(iterable);
|
||||
}
|
||||
|
||||
@@ -874,13 +925,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamDoesNotContain<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).doesNotContain(array);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).doesNotContain(array);
|
||||
}
|
||||
|
||||
@@ -896,14 +947,14 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -918,13 +969,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamDoesNotContainSequence<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).doesNotContainSequence(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, U[] iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] iterable) {
|
||||
return assertThat(stream.collect(collector)).doesNotContainSequence(iterable);
|
||||
}
|
||||
|
||||
@@ -940,7 +991,7 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamDoesNotContainSequence" /* Varargs converted to array. */)
|
||||
ListAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector))
|
||||
.doesNotContainSequence(Refaster.asVarargs(elements));
|
||||
}
|
||||
@@ -957,13 +1008,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamHasSameElementsAs<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).hasSameElementsAs(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).hasSameElementsAs(iterable);
|
||||
}
|
||||
|
||||
@@ -978,13 +1029,13 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamContainsOnly<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsOnly(array);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, U[] array, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] array) {
|
||||
return assertThat(stream.collect(collector)).containsOnly(array);
|
||||
}
|
||||
|
||||
@@ -1000,14 +1051,14 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -1022,25 +1073,25 @@ final class AssertJRules {
|
||||
static final class AssertThatStreamIsSubsetOf<S, T extends S, U extends T> {
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, U[] iterable, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, U[] iterable) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, Iterable<U> iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, Iterable<U> iterable) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(iterable);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, U[] iterable, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, U[] iterable) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(iterable);
|
||||
}
|
||||
|
||||
@@ -1056,14 +1107,14 @@ final class AssertJRules {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
|
||||
IterableAssert<T> before(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends Iterable<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
|
||||
ListAssert<T> before2(
|
||||
Stream<S> stream, @Repeated U elements, Collector<S, ?, ? extends List<T>> collector) {
|
||||
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
|
||||
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
|
||||
}
|
||||
|
||||
@@ -1973,8 +2024,8 @@ final class AssertJRules {
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Above: Generated code.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Organize the code below.
|
||||
|
||||
// XXX: Do the "single Comparable" match shown below.
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
@@ -94,30 +89,4 @@ final class AssertJStringRules {
|
||||
return assertThat(string).doesNotMatch(regex);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatPathContent {
|
||||
@BeforeTemplate
|
||||
AbstractStringAssert<?> before(Path path, Charset charset) throws IOException {
|
||||
return assertThat(Files.readString(path, charset));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractStringAssert<?> after(Path path, Charset charset) {
|
||||
return assertThat(path).content(charset);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatPathContentUtf8 {
|
||||
@BeforeTemplate
|
||||
AbstractStringAssert<?> before(Path path) throws IOException {
|
||||
return assertThat(Files.readString(path));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractStringAssert<?> after(Path path) {
|
||||
return assertThat(path).content(UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user