mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 15:49:33 +00:00
Compare commits
177 Commits
ibabiankou
...
nkooij/tim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ced020ba6 | ||
|
|
389928ce43 | ||
|
|
d88f226b30 | ||
|
|
ba91c6bed7 | ||
|
|
85f402b089 | ||
|
|
ad8c0a472c | ||
|
|
0af127652e | ||
|
|
cede5e451b | ||
|
|
134895090f | ||
|
|
226bfd0cee | ||
|
|
89a3c605fe | ||
|
|
63273a2609 | ||
|
|
79768a2428 | ||
|
|
dd8d094b5a | ||
|
|
4830b5b2cd | ||
|
|
48772a044e | ||
|
|
281534aeca | ||
|
|
42e632e5db | ||
|
|
7febccb7ff | ||
|
|
d5c1c858d5 | ||
|
|
55d2622380 | ||
|
|
f10f2c9209 | ||
|
|
99f85614fd | ||
|
|
be24edadae | ||
|
|
068c03708b | ||
|
|
9c330981ea | ||
|
|
b780c05dc0 | ||
|
|
16955a9cfa | ||
|
|
8fa3ff3702 | ||
|
|
022a3d293e | ||
|
|
81227cdd94 | ||
|
|
04d886c031 | ||
|
|
afb2a28dcf | ||
|
|
dc0f90e981 | ||
|
|
2196bbd8f9 | ||
|
|
f3b81304b9 | ||
|
|
b0d374040a | ||
|
|
45dfc53d40 | ||
|
|
6cb10ffe2f | ||
|
|
92f2b0ab0f | ||
|
|
21388273c5 | ||
|
|
b2e15607c1 | ||
|
|
50e730fb40 | ||
|
|
a844b9e532 | ||
|
|
671ee1eedb | ||
|
|
7fe61c226a | ||
|
|
8fcc91febf | ||
|
|
e00aba12c3 | ||
|
|
0118cc6c10 | ||
|
|
91e009cab0 | ||
|
|
68dca3204e | ||
|
|
e6ccb523fd | ||
|
|
d0533f7190 | ||
|
|
759e7ea2ff | ||
|
|
2578857a37 | ||
|
|
1d28512f09 | ||
|
|
4efd79bfa6 | ||
|
|
d6b28733f6 | ||
|
|
b9884fa6d6 | ||
|
|
4fd8f57430 | ||
|
|
a2559bddda | ||
|
|
7de9bede19 | ||
|
|
85cb997f1b | ||
|
|
d5a78186db | ||
|
|
128e178d37 | ||
|
|
7aef2cfe51 | ||
|
|
5cec0dd508 | ||
|
|
3f1399c139 | ||
|
|
757d5b1d70 | ||
|
|
45cac6105e | ||
|
|
8f8f57fc5b | ||
|
|
902d4f7736 | ||
|
|
4ec349582c | ||
|
|
57836103ea | ||
|
|
bd73243c34 | ||
|
|
9af50d7e0b | ||
|
|
f5ae47fbac | ||
|
|
9ad8c275d3 | ||
|
|
f4e191e33b | ||
|
|
c4e476a731 | ||
|
|
5ca95eb36d | ||
|
|
9204ef0e84 | ||
|
|
ed45be4e15 | ||
|
|
0561c371de | ||
|
|
aa5ad4d25b | ||
|
|
8c0041a94e | ||
|
|
397f9c3df7 | ||
|
|
2ba7bf9f46 | ||
|
|
5b079eef84 | ||
|
|
a2ce053daf | ||
|
|
ba02bad9bf | ||
|
|
d97a20247f | ||
|
|
50970eb932 | ||
|
|
7e7318ad80 | ||
|
|
fb6fe5a96e | ||
|
|
e37da2a1ed | ||
|
|
7bef1c8e67 | ||
|
|
0160eafca0 | ||
|
|
891fecd297 | ||
|
|
7f18bd9030 | ||
|
|
1473a70de8 | ||
|
|
c0c3ce2644 | ||
|
|
100d5c86f7 | ||
|
|
e12f99975b | ||
|
|
791113669f | ||
|
|
564bc7e1d1 | ||
|
|
43bcbeaa98 | ||
|
|
d682b7d41f | ||
|
|
72a124a20e | ||
|
|
326f3328a7 | ||
|
|
ae7068f464 | ||
|
|
066931fe53 | ||
|
|
3874ca9be2 | ||
|
|
84ba3946d9 | ||
|
|
b675ff680f | ||
|
|
8e7d04a24c | ||
|
|
bfc951b61f | ||
|
|
b30562bbd8 | ||
|
|
9be85204ae | ||
|
|
6fbf1b0cb2 | ||
|
|
5a428e6e29 | ||
|
|
71163c0061 | ||
|
|
880be0dbdd | ||
|
|
62fe10f2cd | ||
|
|
f7ec28368a | ||
|
|
e34c2baf7c | ||
|
|
184ba8af51 | ||
|
|
63c3aa8259 | ||
|
|
7daabeee48 | ||
|
|
0acfd8a723 | ||
|
|
000fcefe92 | ||
|
|
1e3ad0fe32 | ||
|
|
b88a668819 | ||
|
|
4c8e125dcb | ||
|
|
4ab5dc4f32 | ||
|
|
7c667334cc | ||
|
|
b4b2afd130 | ||
|
|
4445c93f6e | ||
|
|
5a7d7ff89b | ||
|
|
7ef75e8f07 | ||
|
|
a1f2418805 | ||
|
|
39bfcc75ca | ||
|
|
753cdce29e | ||
|
|
36654883e5 | ||
|
|
d5372934ec | ||
|
|
f810530599 | ||
|
|
6928381403 | ||
|
|
5657a48552 | ||
|
|
50aaf77a9e | ||
|
|
b8e22ffef0 | ||
|
|
17035a1623 | ||
|
|
e7dacd19d7 | ||
|
|
e64af1dde0 | ||
|
|
a58630bccf | ||
|
|
9ab5bbe042 | ||
|
|
4ca75c6cf6 | ||
|
|
7883b31eb6 | ||
|
|
ef751ce785 | ||
|
|
130c3d0bc3 | ||
|
|
c89e3905bf | ||
|
|
21421ce753 | ||
|
|
c39d1251d2 | ||
|
|
9bc732b4fe | ||
|
|
74100b6c41 | ||
|
|
624f2ce753 | ||
|
|
967017eed9 | ||
|
|
b06945b833 | ||
|
|
ef562c1644 | ||
|
|
efbde936dc | ||
|
|
c57653dd5b | ||
|
|
12b03e95b1 | ||
|
|
c2f24ac739 | ||
|
|
4f5ea8beac | ||
|
|
21646ffcb1 | ||
|
|
c58dceb9df | ||
|
|
90ef2f4042 | ||
|
|
459a498d6c |
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: 🐛 Bug report
|
||||
about: Create a report to help us improve.
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
|
||||
<!-- Provide a clear and concise description of what the bug or issue is. -->
|
||||
|
||||
- [ ] I have verified that the issue is reproducible against the latest version
|
||||
of the project.
|
||||
- [ ] I have searched through existing issues to verify that this issue is not
|
||||
already known.
|
||||
|
||||
### Minimal Reproducible Example
|
||||
|
||||
<!-- Provide a clear and concise description of what happened. Please include
|
||||
steps on how to reproduce the issue. -->
|
||||
|
||||
```java
|
||||
If applicable and possible, please replace this section with the code that
|
||||
triggered the isue.
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Logs</summary>
|
||||
|
||||
```sh
|
||||
Please replace this sentence with log output, if applicable.
|
||||
```
|
||||
</details>
|
||||
|
||||
### Expected behavior
|
||||
|
||||
<!-- Provide a clear and concise description of what you expected to happen. -->
|
||||
|
||||
### Setup
|
||||
|
||||
<!-- Please complete the following information: -->
|
||||
|
||||
- Operating system (e.g. MacOS Monterey).
|
||||
- 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
|
||||
|
||||
<!-- Provide any other context about the problem here. -->
|
||||
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
57
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
name: 🚀 Request a new feature
|
||||
about: Suggest a new feature.
|
||||
title: ""
|
||||
labels: new feature
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
### Problem
|
||||
|
||||
<!-- Here, describe the context of the problem that you're facing, and which
|
||||
you'd like to be solved through Error Prone Support. -->
|
||||
|
||||
### Description of the proposed new feature
|
||||
|
||||
<!-- Please indicate the type of improvement. -->
|
||||
|
||||
- [ ] Support a stylistic preference.
|
||||
- [ ] Avoid a common gotcha, or potential problem.
|
||||
|
||||
<!--
|
||||
Here, provide a clear and concise description of the desired change.
|
||||
|
||||
If possible, provide a simple and minimal example using the following format:
|
||||
|
||||
I would like to rewrite the following code:
|
||||
```java
|
||||
// XXX: Write the code to match here.
|
||||
```
|
||||
|
||||
to:
|
||||
```java
|
||||
// XXX: Write the desired code here.
|
||||
```
|
||||
-->
|
||||
|
||||
### Considerations
|
||||
|
||||
<!--
|
||||
Here, mention any other aspects to consider. Relevant questions:
|
||||
|
||||
- If applicable, is the rewrite operation a clear improvement in all cases?
|
||||
- Are there special cases to consider?
|
||||
- Can we further generalize the proposed solution?
|
||||
- Are there alternative solutions we should consider?
|
||||
-->
|
||||
|
||||
### Participation
|
||||
|
||||
<!-- Pull requests are very welcome, and we happily review contributions. Are
|
||||
you up for the challenge? :D -->
|
||||
|
||||
- [ ] I am willing to submit a pull request to implement this improvement.
|
||||
|
||||
### Additional context
|
||||
|
||||
<!-- Provide any other context about the request here. -->
|
||||
29
.github/release.yml
vendored
Normal file
29
.github/release.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- "ignore-changelog"
|
||||
categories:
|
||||
- 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"
|
||||
- "bug fix"
|
||||
- title: ":books: Documentation, test and build improvements"
|
||||
labels:
|
||||
- "documentation"
|
||||
- "chore"
|
||||
- title: ":chart_with_upwards_trend: Dependency upgrades"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- title: "Other changes"
|
||||
labels:
|
||||
- "*"
|
||||
39
.github/workflows/build.yaml
vendored
39
.github/workflows/build.yaml
vendored
@@ -2,15 +2,32 @@ name: Build and verify
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'ibabiankou/experiments'
|
||||
branches: [ master ]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
jdk: [ 11.0.16, 17.0.4 ]
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 11.0.16, 17.0.4, 19 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-12
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: ubuntu-22.04
|
||||
jdk: 20-ea
|
||||
distribution: zulu
|
||||
experimental: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
# We run the build twice for each supported JDK: once against the
|
||||
# original Error Prone release, using only Error Prone checks available
|
||||
@@ -18,20 +35,20 @@ jobs:
|
||||
# additionally enabling all checks defined in this project and any
|
||||
# Error Prone checks available only from other artifact repositories.
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.0.2
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.4.1
|
||||
uses: actions/setup-java@v3.6.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
distribution: temurin
|
||||
distribution: ${{ matrix.distribution }}
|
||||
cache: maven
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
- name: Build project against vanilla Error Prone
|
||||
run: mvn -T1C install
|
||||
- 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 project snapshots
|
||||
- name: Remove installed project artifacts
|
||||
run: mvn build-helper:remove-project-artifact
|
||||
|
||||
# XXX: Enable Codecov once we "go public".
|
||||
|
||||
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.120.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@v2.1.2
|
||||
- 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.4
|
||||
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.2
|
||||
@@ -1,4 +1,8 @@
|
||||
-XX:ReservedCodeCacheSize=512m
|
||||
-XX:SoftRefLRUPolicyMSPerMB=10
|
||||
-XX:+UseParallelGC
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
|
||||
@@ -6,5 +10,4 @@
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
|
||||
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
|
||||
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
|
||||
|
||||
71
CONTRIBUTING.md
Normal file
71
CONTRIBUTING.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for checking this document! This project is free software, and we
|
||||
(the maintainers) encourage and value any contribution.
|
||||
|
||||
Here are some guidelines to help you get started.
|
||||
|
||||
## 🐛 Reporting a bug
|
||||
|
||||
Like any non-trivial piece of software, this library is probably not bug-free.
|
||||
If you found a bug, feel free to [report the issue][error-prone-support-issues]
|
||||
on GitHub.
|
||||
|
||||
Before doing so, please:
|
||||
- Verify that the issue is reproducible against the latest version of the
|
||||
project.
|
||||
- Search through the existing set of issues to see whether the problem is
|
||||
already known. With some luck a solution is already in place, or a workaround
|
||||
may have been provided.
|
||||
|
||||
When filing a bug report, please include the following:
|
||||
- Any relevant information about your environment. This should generally
|
||||
include the output of `java --version`, as well as the version of Error Prone
|
||||
you're using.
|
||||
- A description of what is going on (e.g. logging output, stacktraces).
|
||||
- A minimum reproducible example, so that other developers can try to reproduce
|
||||
(and optionally fix) the bug.
|
||||
- Any additional information that may be relevant.
|
||||
|
||||
## 💡 Reporting an improvement
|
||||
|
||||
If you would like to see an improvement, you can file a [GitHub
|
||||
issue][error-prone-support-issues]. This is also a good idea when you're
|
||||
already working towards opening a pull request, as this allows for discussion
|
||||
around the idea.
|
||||
|
||||
## 🚀 Opening a pull request
|
||||
|
||||
All submissions, including submissions by project members, require approval by
|
||||
at least two reviewers. We use [GitHub pull
|
||||
requests][error-prone-support-pulls] for this purpose.
|
||||
|
||||
Before opening a pull request, please check whether there are any existing
|
||||
(open or closed) issues or pull requests addressing the same problem. This
|
||||
avoids double work or lots of time spent on a solution that may ultimately not
|
||||
be accepted. When in doubt, make sure to first raise an
|
||||
[issue][error-prone-support-issues] to discuss the idea.
|
||||
|
||||
To the extent possible, the pull request process guards our coding guidelines.
|
||||
Some pointers:
|
||||
- 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
|
||||
it must not "get in the way". So for a check that addresses a relatively
|
||||
trivial stylistic concern it is doubly important that the violations it
|
||||
detects can be auto-patched.
|
||||
- Make sure you have read Error Prone's [criteria for new
|
||||
checks][error-prone-criteria]. Most guidelines described there apply to this
|
||||
project as well, except that this project _does_ focus quite heavy on style
|
||||
enforcement. But that just makes the previous point doubly important.
|
||||
- Make sure that a check's [(mutation) test
|
||||
coverage][error-prone-support-mutation-tests] is or remains about as high as
|
||||
it can be. Not only does this lead to better tests, it also points out
|
||||
opportunities to simplify the code.
|
||||
- Please restrict the scope of a pull request to a single feature or fix. Don't
|
||||
sneak in unrelated changes; instead just open more than one pull request 😉.
|
||||
|
||||
[error-prone-criteria]: https://errorprone.info/docs/criteria
|
||||
[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-mutation-tests.sh
|
||||
[error-prone-support-pulls]: https://github.com/PicnicSupermarket/error-prone-support/pulls
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2022 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
231
README.md
Normal file
231
README.md
Normal file
@@ -0,0 +1,231 @@
|
||||
<div align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="website/assets/images/logo-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="website/assets/images/logo.svg">
|
||||
<img alt="Error Prone Support logo" src="website/assets/images/logo.svg" width="50%">
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
# Error Prone Support
|
||||
|
||||
Error Prone Support is a [Picnic][picnic-blog]-opinionated extension of
|
||||
Google's [Error Prone][error-prone-orig-repo]. It aims to improve code quality,
|
||||
focussing on maintainability, consistency and avoidance of common pitfalls.
|
||||
|
||||
> Error Prone is a static analysis tool for Java that catches common
|
||||
> programming mistakes at compile-time.
|
||||
|
||||
Read more on how Picnic uses Error Prone (Support) in the blog post [_Picnic
|
||||
loves Error Prone: producing high-quality and consistent Java
|
||||
code_][picnic-blog-ep-post].
|
||||
|
||||
[![Maven Central][maven-central-badge]][maven-central-search]
|
||||
[![Reproducible Builds][reproducible-builds-badge]][reproducible-builds-report]
|
||||
[![GitHub Actions][github-actions-build-badge]][github-actions-build-master]
|
||||
[![License][license-badge]][license]
|
||||
[![PRs Welcome][pr-badge]][contributing]
|
||||
|
||||
[Getting started](#-getting-started) •
|
||||
[Developing Error Prone Support](#-developing-error-prone-support) •
|
||||
[How it works](#-how-it-works) • [Contributing](#%EF%B8%8F-contributing)
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Getting started
|
||||
|
||||
### Installation
|
||||
|
||||
This library is built on top of [Error Prone][error-prone-orig-repo]. To use
|
||||
it:
|
||||
|
||||
1. First, follow Error Prone's [installation
|
||||
guide][error-prone-installation-guide].
|
||||
2. Next, edit your `pom.xml` file to add one or more Error Prone Support
|
||||
modules to the `annotationProcessorPaths` of the `maven-compiler-plugin`:
|
||||
|
||||
```xml
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<!-- Error Prone itself. -->
|
||||
<path>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>error_prone_core</artifactId>
|
||||
<version>${error-prone.version}</version>
|
||||
</path>
|
||||
<!-- Error Prone Support's additional bug checkers. -->
|
||||
<path>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
<version>${error-prone-support.version}</version>
|
||||
</path>
|
||||
<!-- Error Prone Support's Refaster rules. -->
|
||||
<path>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>refaster-runner</artifactId>
|
||||
<version>${error-prone-support.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>
|
||||
-Xplugin:ErrorProne
|
||||
<!-- Add other Error Prone flags here. See
|
||||
https://errorprone.info/docs/flags. -->
|
||||
</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>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
```
|
||||
|
||||
<!-- XXX: Reference `oss-parent`'s `pom.xml` once that project also uses Error
|
||||
Prone Support. Alternatively reference this project's `self-check` profile
|
||||
definition. -->
|
||||
|
||||
### Seeing it in action
|
||||
|
||||
Consider the following example code:
|
||||
|
||||
```java
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class Example {
|
||||
static BigDecimal getNumber() {
|
||||
return BigDecimal.valueOf(0);
|
||||
}
|
||||
|
||||
public ImmutableSet<Integer> getSet() {
|
||||
ImmutableSet<Integer> set = ImmutableSet.of(1);
|
||||
return ImmutableSet.copyOf(set);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the [installation](#installation) was successful, then building the above
|
||||
code with Maven should yield two compiler warnings:
|
||||
|
||||
```sh
|
||||
$ mvn clean install
|
||||
...
|
||||
[INFO] Example.java:[9,34] [Refaster Rule] BigDecimalRules.BigDecimalZero: Refactoring opportunity
|
||||
(see https://error-prone.picnic.tech/refasterrules/BigDecimalRules#BigDecimalZero)
|
||||
Did you mean 'return BigDecimal.ZERO;'?
|
||||
...
|
||||
[WARNING] Example.java:[13,35] [IdentityConversion] This method invocation appears redundant; remove it or suppress this warning and add a comment explaining its purpose
|
||||
(see https://error-prone.picnic.tech/bugpatterns/IdentityConversion)
|
||||
Did you mean 'return set;' or '@SuppressWarnings("IdentityConversion") public ImmutableSet<Integer> getSet() {'?
|
||||
...
|
||||
```
|
||||
|
||||
Two things are kicking in here:
|
||||
|
||||
1. An Error Prone [`BugChecker`][error-prone-bugchecker] that flags unnecessary
|
||||
[identity conversions][bug-checks-identity-conversion].
|
||||
2. A [Refaster][refaster] rule capable of
|
||||
[rewriting][refaster-rules-bigdecimal] expressions of the form
|
||||
`BigDecimal.valueOf(0)` and `new BigDecimal(0)` to `BigDecimal.ZERO`.
|
||||
|
||||
Be sure to check out all [bug checks][bug-checks] and [refaster
|
||||
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. Some
|
||||
relevant flags:
|
||||
|
||||
- `-Dverification.warn` makes the warnings and errors emitted by various
|
||||
plugins and the Java compiler non-fatal, where possible.
|
||||
- `-Dverification.skip` disables various non-essential plugins and compiles the
|
||||
code with minimal checks (i.e. without linting, Error Prone checks, etc.).
|
||||
- `-Dversion.error-prone=some-version` runs the build using the specified
|
||||
version of Error Prone. This is useful e.g. when testing a locally built
|
||||
Error Prone SNAPSHOT.
|
||||
- `-Perror-prone-fork` runs the build using Picnic's [Error Prone
|
||||
fork][error-prone-fork-repo], hosted on [Jitpack][error-prone-fork-jitpack].
|
||||
This fork generally contains a few changes on top of the latest Error Prone
|
||||
release.
|
||||
- `-Pself-check` runs the checks defined by this project against itself.
|
||||
Pending a release of [google/error-prone#3301][error-prone-pull-3301], this
|
||||
flag must currently be used in combination with `-Perror-prone-fork`.
|
||||
|
||||
Some other commands one may find relevant:
|
||||
|
||||
- `mvn fmt:format` formats the code using
|
||||
[`google-java-format`][google-java-format].
|
||||
- `./run-mutation-tests.sh` runs mutation tests using [PIT][pitest]. The
|
||||
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:
|
||||
|
||||
```
|
||||
java: exporting a package from system module jdk.compiler is not allowed with --release
|
||||
```
|
||||
|
||||
If this happens, go to _Settings -> Build, Execution, Deployment -> Compiler ->
|
||||
Java Compiler_ and deselect the option _Use '--release' option for
|
||||
cross-compilation (Java 9 and later)_. See [IDEA-288052][idea-288052] for
|
||||
details.
|
||||
|
||||
## 💡 How it works
|
||||
|
||||
This project provides additional [`BugChecker`][error-prone-bugchecker]
|
||||
implementations.
|
||||
|
||||
<!-- XXX: Extend this section. -->
|
||||
|
||||
## ✍️ Contributing
|
||||
|
||||
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].
|
||||
|
||||
[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
|
||||
[contributing]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md
|
||||
[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
|
||||
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
|
||||
[error-prone-orig-repo]: https://github.com/google/error-prone
|
||||
[error-prone-pull-3301]: https://github.com/google/error-prone/pull/3301
|
||||
[github-actions-build-badge]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml/badge.svg
|
||||
[github-actions-build-master]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/build.yaml?query=branch%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
|
||||
[license]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/LICENSE.md
|
||||
[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
|
||||
[picnic-blog]: https://blog.picnic.nl
|
||||
[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
|
||||
[refaster]: https://errorprone.info/docs/refaster
|
||||
[refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java
|
||||
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
|
||||
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
|
||||
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
|
||||
@@ -11,71 +11,22 @@ request.
|
||||
|
||||
### Building
|
||||
|
||||
This is a [Maven][maven] project, so running `mvn clean install` performs a
|
||||
full clean build. Some relevant flags:
|
||||
- `-Dverification.warn` makes the warnings and errors emitted by various
|
||||
plugins and the Java compiler non-fatal, where possible.
|
||||
- `-Dverification.skip` disables various non-essential plugins and compiles the
|
||||
code with minimal checks (i.e. without linting, Error Prone checks, etc.)
|
||||
- `-Dversion.error-prone=some-version` runs the build using the specified
|
||||
version of Error Prone. This is useful e.g. when testing a locally built
|
||||
Error Prone SNAPSHOT.
|
||||
- `-Perror-prone-fork` run the build using Picnic's [Error Prone
|
||||
fork][error-prone-fork-repo], hosted on [Jitpack][error-prone-fork-jitpack].
|
||||
This fork generally contains a few changes on top of the latest Error Prone
|
||||
release.
|
||||
|
||||
Two other goals that one may find relevant:
|
||||
- `mvn fmt:format` formats the code using
|
||||
[`google-java-format`][google-java-format].
|
||||
- `mvn pitest:mutationCoverage` runs mutation tests using [PIT][pitest]. The
|
||||
results can be reviewed by opening the respective
|
||||
`target/pit-reports/index.html` files. For more information check the [PIT
|
||||
Maven plugin][pitest-maven].
|
||||
|
||||
When loading the project in IntelliJ IDEA (and perhaps other IDEs) errors about
|
||||
the inaccessibility of `com.sun.tools.javac.*` classes may be reported. If this
|
||||
happens, configure your IDE to enable the `add-exports` profile.
|
||||
See the main [readme][main-readme].
|
||||
|
||||
### Contribution guidelines
|
||||
|
||||
To the extend possible, the pull request process guards our coding guidelines.
|
||||
Some pointers:
|
||||
- Checks should we _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
|
||||
it must not "get in the way". So for a check which addresses a relatively
|
||||
trivial stylistic concern it is doubly important that the violations it
|
||||
detects can be auto-patched.
|
||||
- Make sure you have read Error Prone's [criteria for new
|
||||
checks][error-prone-criteria]. Most guidelines described there apply to this
|
||||
project as well, except that this project _does_ focus quite heavy on style
|
||||
enforcement. But that just makes the previous point doubly important.
|
||||
- Make sure that a check's (mutation) coverage is or remains about as high as
|
||||
it can be. Not only does this lead to better tests, it also points out
|
||||
opportunities to simplify the code.
|
||||
- Please restrict the scope of a pull request to a single feature or fix. Don't
|
||||
sneak in unrelated changes.
|
||||
- When in doubt about whether a pull request will be accepted, please first
|
||||
file an issue to discuss it.
|
||||
See our [contributing guidelines][main-contributing].
|
||||
|
||||
### Our wishlist
|
||||
|
||||
We expect the following tasks to help improve the quality of this open source
|
||||
project:
|
||||
|
||||
- Publish the artifact to Maven Central, then document the coordinates in this
|
||||
`README.md`.
|
||||
- Document how to enable the checks.
|
||||
- Document how to apply patches.
|
||||
- Document each of the checks.
|
||||
- Add Travis CI, [SonarQube][sonarcloud] and [Codecov][codecov]
|
||||
integrations.
|
||||
- Investigate whether it makes sense to include license headers in each file.
|
||||
If so, set that up and enforce it.
|
||||
- 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.)
|
||||
- Add relevant "badges" at the top of this `README.md`.
|
||||
- Auto-generate a website listing each of the checks, just like the Error Prone
|
||||
[bug patterns page][error-prone-bug-patterns]. The [Error Prone
|
||||
repository][error-prone-repo] contains code for this.
|
||||
@@ -93,7 +44,7 @@ project:
|
||||
- Improve an existing check (see `XXX`-marked comments in the code) or write a
|
||||
new one (see the list of suggestions below).
|
||||
|
||||
### Ideas for new checks
|
||||
### BugChecker extension ideas
|
||||
|
||||
The following is a list of checks we'd like to see implemented:
|
||||
|
||||
@@ -103,7 +54,7 @@ The following is a list of checks we'd like to see implemented:
|
||||
signature groups. Using Error Prone's method matchers forbidden method calls
|
||||
can easily be identified. But Error Prone can go one step further by
|
||||
auto-patching violations. For each violation two fixes can be proposed: a
|
||||
purely behavior-preserving fix which makes the platform-dependent behavior
|
||||
purely behavior-preserving fix, which makes the platform-dependent behavior
|
||||
explicit, and another which replaces the platform-dependent behavior with the
|
||||
preferred alternative. (Such as using `UTF-8` instead of the system default
|
||||
charset.)
|
||||
@@ -113,128 +64,128 @@ The following is a list of checks we'd like to see implemented:
|
||||
functionality.
|
||||
- A subset of the refactor operations provided by the Eclipse-specific
|
||||
[AutoRefactor][autorefactor] plugin.
|
||||
- A check which replaces fully qualified types with simple types in contexts
|
||||
- A check that replaces fully qualified types with simple types in contexts
|
||||
where this does not introduce ambiguity. Should consider both actual Java
|
||||
code and Javadoc `@link` references.
|
||||
- A check which simplifies array expressions. It would replace empty array
|
||||
- A check that simplifies array expressions. It would replace empty array
|
||||
expressions of the form `new int[] {}` with `new int[0]`. Statements of the
|
||||
form `byte[] arr = new byte[] {'c'};` would be shortened to `byte[] arr =
|
||||
{'c'};`.
|
||||
- A check which replaces expressions of the form `String.format("some prefix
|
||||
- A check that replaces expressions of the form `String.format("some prefix
|
||||
%s", arg)` with `"some prefix " + arg`, and similar for simple suffixes. Can
|
||||
perhaps be generalized further, though it's unclear how far. (Well, a
|
||||
`String.format` call without arguments can certainly be simplified, too.)
|
||||
- A check which replaces single-character strings with `char`s where possible.
|
||||
- A check that replaces single-character strings with `char`s where possible.
|
||||
For example as argument to `StringBuilder.append` and in string
|
||||
concatenations.
|
||||
- A check which adds or removes the first `Locale` argument to `String.format`
|
||||
- A check that adds or removes the first `Locale` argument to `String.format`
|
||||
and similar calls as necessary. (For example, a format string containing only
|
||||
`%s` placeholders is locale-insensitive unless any of the arguments is a
|
||||
`Formattable`, while `%f` placeholders _are_ locale-sensitive.)
|
||||
- A check which replaces `String.replaceAll` with `String.replace` if the first
|
||||
- A check that replaces `String.replaceAll` with `String.replace` if the first
|
||||
argument is certainly not a regular expression. And if both arguments are
|
||||
single-character strings then the `(char, char)` overload can be invoked
|
||||
instead.
|
||||
- A check which flags (and ideally, replaces) `try-finally` constructs with
|
||||
- A check that flags (and ideally, replaces) `try-finally` constructs with
|
||||
equivalent `try-with-resources` constructs.
|
||||
- A check which drops exceptions declared in `throws` clauses if they are (a)
|
||||
- A check that drops exceptions declared in `throws` clauses if they are (a)
|
||||
not actually thrown and (b) the associated method cannot be overridden.
|
||||
- A check which tries to statically import certain methods whenever used, if
|
||||
- A check that tries to statically import certain methods whenever used, if
|
||||
possible. The set of targeted methods should be configurable, but may default
|
||||
to e.g. `java.util.Function.identity()`, the static methods exposed by
|
||||
`java.util.stream.Collectors` and the various Guava collector factory
|
||||
methods.
|
||||
- A check which replaces `new Random().someMethod()` calls with
|
||||
`ThreadLocalRandom.current().someMethod()` calls, so as to avoid unnecessary
|
||||
- A check that replaces `new Random().someMethod()` calls with
|
||||
`ThreadLocalRandom.current().someMethod()` calls, to avoid unnecessary
|
||||
synchronization.
|
||||
- A check which drops `this.` from `this.someMethod()` calls and which
|
||||
- A check that drops `this.` from `this.someMethod()` calls and which
|
||||
optionally does the same for fields, if no ambiguity arises.
|
||||
- A check which replaces `Integer.valueOf` calls with `Integer.parseInt` or
|
||||
vice versa in order to prevent auto (un)boxing, and likewise for other number
|
||||
- A check that replaces `Integer.valueOf` calls with `Integer.parseInt` or vice
|
||||
versa in order to prevent auto (un)boxing, and likewise for other number
|
||||
types.
|
||||
- A check which flags nullable collections.
|
||||
- A check which flags `AutoCloseable` resources not managed by a
|
||||
- A check that flags nullable collections.
|
||||
- A check that flags `AutoCloseable` resources not managed by a
|
||||
`try-with-resources` construct. Certain subtypes, such as jOOQ's `DSLContext`
|
||||
should be excluded.
|
||||
- A check which flags `java.time` methods which implicitly consult the system
|
||||
- A check that flags `java.time` methods which implicitly consult the system
|
||||
clock, suggesting that a passed-in `Clock` is used instead.
|
||||
- A check which flags public methods on public classes which reference
|
||||
- A check that flags public methods on public classes which reference
|
||||
non-public types. This can cause `IllegalAccessError`s and
|
||||
`BootstrapMethodError`s at runtime.
|
||||
- A check which swaps the LHS and RHS in expressions of the form
|
||||
- A check that swaps the LHS and RHS in expressions of the form
|
||||
`nonConstant.equals(someNonNullConstant)`.
|
||||
- A check which annotates methods which only throw an exception with
|
||||
- A check that annotates methods which only throw an exception with
|
||||
`@Deprecated` or ` @DoNotCall`.
|
||||
- A check which flags imports from other test classes.
|
||||
- A Guava-specific check which replaces `Joiner.join` calls with `String.join`
|
||||
- A check that flags imports from other test classes.
|
||||
- A Guava-specific check that replaces `Joiner.join` calls with `String.join`
|
||||
calls in those cases where the latter is a proper substitute for the former.
|
||||
- A Guava-specific check which flags `{Immutable,}Multimap` type usages
|
||||
where `{Immutable,}{List,Set}Multimap` would be more appropriate.
|
||||
- A Guava-specific check which rewrites `if (conditional) { throw new
|
||||
- A Guava-specific check that flags `{Immutable,}Multimap` type usages where
|
||||
`{Immutable,}{List,Set}Multimap` would be more appropriate.
|
||||
- A Guava-specific check that rewrites `if (conditional) { throw new
|
||||
IllegalArgumentException(); }` and variants to an equivalent `checkArgument`
|
||||
statement. Idem for other exception types.
|
||||
- A Guava-specific check which replaces simple anonymous `CacheLoader` subclass
|
||||
- A Guava-specific check that replaces simple anonymous `CacheLoader` subclass
|
||||
declarations with `CacheLoader.from(someLambda)`.
|
||||
- A Spring-specific check which enforces that methods with the `@Scheduled`
|
||||
- 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 which enforces that `@RequestMapping` annotations,
|
||||
- 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 which looks for classes in which all
|
||||
`@RequestMapping` annotations (and the various aliases) specify the same
|
||||
`path`/`value` property and then moves that path to the class level.
|
||||
- A Spring-specific check which flags `@Value("some.property")` annotations, as
|
||||
- A Spring-specific check that looks for classes in which all `@RequestMapping`
|
||||
annotations (and the various aliases) specify the same `path`/`value`
|
||||
property and then moves that path to the class level.
|
||||
- A Spring-specific check that flags `@Value("some.property")` annotations, as
|
||||
these almost certainly should be `@Value("${some.property}")`.
|
||||
- A Spring-specific check which drops the `required` attribute from
|
||||
- A Spring-specific check that drops the `required` attribute from
|
||||
`@RequestParam` annotations when the `defaultValue` attribute is also
|
||||
specified.
|
||||
- A Spring-specific check which rewrites a class which uses field injection to
|
||||
- A Spring-specific check that rewrites a class which uses field injection to
|
||||
one which uses constructor injection. This check wouldn't be strictly
|
||||
behavior preserving, but could be used for a one-off code base migration.
|
||||
- A Spring-specific check which disallows field injection, except in
|
||||
- A Spring-specific check that disallows field injection, except in
|
||||
`AbstractTestNGSpringContextTests` subclasses. (One known edge case:
|
||||
self-injections so that a bean can call itself through an implicit proxy.)
|
||||
- A Spring-specific check which verifies that public methods on all classes
|
||||
- A Spring-specific check that verifies that public methods on all classes
|
||||
whose name matches a certain pattern, e.g. `.*Service`, are annotated
|
||||
`@Secured`.
|
||||
- A Spring-specific check which verifies that annotations such as
|
||||
- A Spring-specific check that verifies that annotations such as
|
||||
`@RequestParam` are only present in `@RestController` classes.
|
||||
- A Spring-specific check which disallows `@ResponseStatus` on MVC endpoint
|
||||
- A Spring-specific check that disallows `@ResponseStatus` on MVC endpoint
|
||||
methods, as this prevents communication of error status codes.
|
||||
- A Hibernate Validator-specific check which looks for `@UnwrapValidatedValue`
|
||||
- A Hibernate Validator-specific check that looks for `@UnwrapValidatedValue`
|
||||
usages and migrates the associated constraint annotations to the generic type
|
||||
argument to which they (are presumed to) apply.
|
||||
- A TestNG-specific check which drops method-level `@Test` annotations if a
|
||||
- A TestNG-specific check that drops method-level `@Test` annotations if a
|
||||
matching/more specific annotation is already present at the class level.
|
||||
- A TestNG-specific check which enforces that all tests are in a group.
|
||||
- A TestNG-specific check which flags field assignments in
|
||||
- A TestNG-specific check that enforces that all tests are in a group.
|
||||
- A TestNG-specific check that flags field assignments in
|
||||
`@BeforeMethod`-annotated methods unless the class is annotated
|
||||
`@Test(singleThreaded = true)`.
|
||||
- A TestNG-specific check which flags usages of the `expectedExceptions`
|
||||
- A TestNG-specific check that flags usages of the `expectedExceptions`
|
||||
attribute of the `@Test` annotation, pointing to `assertThrows`.
|
||||
- A Jongo-specific check which disallows the creation of sparse indices, in
|
||||
- A Jongo-specific check that disallows the creation of sparse indices, in
|
||||
favour of partial indices.
|
||||
- An Immutables-specific check which replaces
|
||||
- An Immutables-specific check that replaces
|
||||
`checkState`/`IllegalStateException` usages inside a `@Value.Check`-annotated
|
||||
method with `checkArgument`/`IllegalArgument`, since the method is invoked
|
||||
when a caller attempts to create an immutable instance.
|
||||
- An Immutables-specific check which disallows references to collection types
|
||||
- An Immutables-specific check that disallows references to collection types
|
||||
other than the Guava immutable collections, including inside generic type
|
||||
arguments.
|
||||
- An SLF4J-specific check which drops or adds a trailing dot from log messages,
|
||||
- An SLF4J-specific check that drops or adds a trailing dot from log messages,
|
||||
as applicable.
|
||||
- A Mockito-specific check which identifies sequences of statements which mock
|
||||
a significant number of methods on a single object with "default data"; such
|
||||
- A Mockito-specific check that identifies sequences of statements which mock a
|
||||
significant number of methods on a single object with "default data"; such
|
||||
constructions can often benefit from a different type of default answer, such
|
||||
as `Answers.RETURNS_MOCKS`.
|
||||
- An RxJava-specific check which flags `.toCompletable()` calls on expressions
|
||||
- An RxJava-specific check that flags `.toCompletable()` calls on expressions
|
||||
of type `Single<Completable>` etc., as most likely
|
||||
`.flatMapCompletable(Functions.identity())` was meant instead. Idem for other
|
||||
variations.
|
||||
- An RxJava-specific check which flags `expr.firstOrError()` calls and suggests
|
||||
- An RxJava-specific check that flags `expr.firstOrError()` calls and suggests
|
||||
`expr.switchIfEmpty(Single.error(...))`, so that an application-specific
|
||||
exception is thrown instead of `NoSuchElementException`.
|
||||
- An RxJava-specific check which flags use of `#assertValueSet` without
|
||||
- An RxJava-specific check that flags use of `#assertValueSet` without
|
||||
`#assertValueCount`, as the former method doesn't do what one may intuitively
|
||||
expect it to do. See ReactiveX/RxJava#6151.
|
||||
|
||||
@@ -247,6 +198,7 @@ but on the flip side Refaster is much less expressive. While this gap can never
|
||||
be fully closed, there are some ways in which Refaster's scope of utility could
|
||||
be extended. The following is a non-exhaustive list of ideas on how to extend
|
||||
Refaster's expressiveness:
|
||||
|
||||
- Allow more control over _which_ methods are statically imported by
|
||||
`@UseImportPolicy`. Sometimes the `@AfterTemplate` contains more than one
|
||||
static method invocation, and only a subset should be statically imported.
|
||||
@@ -261,9 +213,9 @@ Refaster's expressiveness:
|
||||
pure expression. Introduce a way to express such a constraint. For example,
|
||||
rewriting `optional1.map(Optional::of).orElse(optional2)` to `optional1.or(()
|
||||
-> optional2)` is not behavior preserving if evaluation of `optional2` has
|
||||
side-effects.
|
||||
side effects.
|
||||
- Similarly, certain refactoring operations are only valid if one of the
|
||||
matches expressions is not `@Nullable`. It'd be nice to be able to express
|
||||
matched expressions is not `@Nullable`. It'd be nice to be able to express
|
||||
this.
|
||||
- Generalize `@Placeholder` support such that rules can reference e.g. "any
|
||||
concrete unary method". This would allow refactorings such as
|
||||
@@ -284,42 +236,38 @@ Refaster's expressiveness:
|
||||
- Provide a way to express transformations of compile-time constants. This
|
||||
would allow one to e.g. rewrite single-character strings to chars or vice
|
||||
versa, thereby accommodating a target API. Another example would be to
|
||||
replace SLF4J's `{}` place holders with `%s` or vice versa. Yet another
|
||||
replace SLF4J's `{}` placeholders with `%s` or vice versa. Yet another
|
||||
example would be to rewrite `BigDecimal.valueOf("<some-long-value>")` to
|
||||
`BigDecimal.valueOf(theParsedLongValue)`.
|
||||
- More generally, investigate ways to plug in in fully dynamic behavior, e.g.
|
||||
by providing hooks using which arbitrary predicates/transformations can be
|
||||
plugged in. The result would be a Refaster/`BugChecker` hybrid. A feature
|
||||
such as this could form the basis for many other features listed here. (As a
|
||||
concrete example, consider the ability to reference
|
||||
- More generally, investigate ways to plug in fully dynamic behavior, e.g. by
|
||||
providing hooks which enable plugging in arbitrary
|
||||
predicates/transformations. The result would be a Refaster/`BugChecker`
|
||||
hybrid. A feature like this could form the basis for many other features
|
||||
listed here. (As a concrete example, consider the ability to reference
|
||||
`com.google.errorprone.matchers.Matcher` implementations.)
|
||||
- Provide a way to match lambda expressions and method references which match a
|
||||
specified functional interface. This would allow rewrites such as
|
||||
`Mono.fromCallable(this::doesNotThrowCheckException)` ->
|
||||
`Mono.fromSupplier(this::doesNotThrowCheckException)`.
|
||||
- Provide an extension API using which methods or expressions can be defined
|
||||
based on functional properties. A motivating example is the Java Collections
|
||||
- Provide an extension API that enables defining methods or expressions based
|
||||
on functional properties. A motivating example is the Java Collections
|
||||
framework, which allows many ways to define (im)mutable (un)ordered
|
||||
collections with(out) duplicates. One could then express things like "match
|
||||
any method call with collects its inputs into an immutable ordered list". An
|
||||
any method call that collects its inputs into an immutable ordered list". An
|
||||
enum analogous to `java.util.stream.Collector.Characteristics` could be used.
|
||||
Out of the box JDK and Guava collection factory methods could be classified,
|
||||
with the user having the option to extend the classification.
|
||||
- Refaster currently unconditionally ignores expressions containing comments.
|
||||
Provide two additional modes: (a) match and drop the comments or (b)
|
||||
transport the comments to before/after the replaced expression.
|
||||
- Extend Refaster to drop imports that come become unnecessary as a result of a
|
||||
refactoring. This e.g. allows one to replace a statically import TestNG
|
||||
- Extend Refaster to drop imports that become unnecessary as a result of a
|
||||
refactoring. This e.g. allows one to replace a statically imported TestNG
|
||||
`fail(...)` invocation with a statically imported equivalent AssertJ
|
||||
`fail(...)` invocation. (Observe that without an impor cleanup this
|
||||
`fail(...)` invocation. (Observe that without an import cleanup this
|
||||
replacement would cause a compilation error.)
|
||||
- Extend the `@Repeated` match semantics such that it also covers non-varargs
|
||||
methods. For a motivating example see google/error-prone#568.
|
||||
- When matching explicit type references, also match super types. For a
|
||||
motivating example, see the two subtly difference loop definitions in
|
||||
`CollectionRemoveAllFromCollectionBlock`.
|
||||
motivating example, see the two subtly different loop definitions in
|
||||
`CollectionRemoveAllFromCollectionExpression`.
|
||||
- Figure out why Refaster sometimes doesn't match the correct generic overload.
|
||||
See the `AssertThatIterableHasOneComparableElementEqualTo` template for an
|
||||
See the `AssertThatIterableHasOneComparableElementEqualTo` rule for an
|
||||
example.
|
||||
|
||||
[autorefactor]: https://autorefactor.org
|
||||
@@ -327,16 +275,12 @@ Refaster's expressiveness:
|
||||
[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-criteria]: https://errorprone.info/docs/criteria
|
||||
[error-prone-fork-jitpack]: https://jitpack.io/#PicnicSupermarket/error-prone
|
||||
[error-prone-fork-repo]: https://github.com/PicnicSupermarket/error-prone
|
||||
[error-prone]: https://errorprone.info
|
||||
[error-prone-repo]: https://github.com/google/error-prone
|
||||
[forbidden-apis]: https://github.com/policeman-tools/forbidden-apis
|
||||
[fossa]: https://fossa.io
|
||||
[google-java-format]: https://github.com/google/google-java-format
|
||||
[maven]: https://maven.apache.org
|
||||
[main-contributing]: ../CONTRIBUTING.md
|
||||
[main-readme]: ../README.md
|
||||
[modernizer-maven-plugin]: https://github.com/gaul/modernizer-maven-plugin
|
||||
[sonarcloud]: https://sonarcloud.io
|
||||
[pitest]: https://pitest.org
|
||||
[pitest-maven]: https://pitest.org/quickstart/maven
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -41,15 +41,13 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
<!-- This dependency is declared only as a hint to Maven that
|
||||
compilation depends on it; see the `maven-compiler-plugin`'s
|
||||
`annotationProcessorPaths` configuration below. -->
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -66,16 +64,6 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.errorprone</groupId>
|
||||
<artifactId>javac</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
@@ -105,6 +93,11 @@
|
||||
<artifactId>reactor-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
@@ -130,11 +123,6 @@
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
@@ -145,6 +133,16 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.immutables</groupId>
|
||||
<artifactId>value-annotations</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
@@ -175,6 +173,11 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
@@ -205,15 +208,6 @@
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.spotify.fmt</groupId>
|
||||
<artifactId>fmt-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<additionalSourceDirectories>
|
||||
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
|
||||
</additionalSourceDirectories>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
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;
|
||||
@@ -22,19 +23,22 @@ import com.sun.tools.javac.code.Symbol;
|
||||
import java.util.Map;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
|
||||
/** A {@link BugChecker} which flags ambiguous {@code @JsonCreator}s in enums. */
|
||||
/** A {@link BugChecker} that flags ambiguous {@code @JsonCreator}s in enums. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "AmbiguousJsonCreator",
|
||||
summary = "`JsonCreator.Mode` should be set for single-argument creators",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "AmbiguousJsonCreator",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class AmbiguousJsonCreatorCheck extends BugChecker implements AnnotationTreeMatcher {
|
||||
public final class AmbiguousJsonCreator extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
|
||||
isType("com.fasterxml.jackson.annotation.JsonCreator");
|
||||
|
||||
/** Instantiates a new {@link AmbiguousJsonCreator} instance. */
|
||||
public AmbiguousJsonCreator() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
if (!IS_JSON_CREATOR_ANNOTATION.matches(tree, state)) {
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
@@ -8,6 +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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -21,21 +22,21 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags AssertJ {@code isEqualTo(null)} checks for simplification.
|
||||
* A {@link BugChecker} that flags AssertJ {@code isEqualTo(null)} checks for simplification.
|
||||
*
|
||||
* <p>This bug checker cannot be replaced with a simple Refaster template, as the Refaster approach
|
||||
* <p>This bug checker cannot be replaced with a simple Refaster rule, as the Refaster approach
|
||||
* would require that all overloads of {@link org.assertj.core.api.Assert#isEqualTo(Object)} (such
|
||||
* as {@link org.assertj.core.api.AbstractStringAssert#isEqualTo(String)}) are explicitly
|
||||
* enumerated. This bug checker generically matches all such current and future overloads.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "AssertJIsNull",
|
||||
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "AssertJIsNull",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class AssertJIsNullCheck extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
public final class AssertJIsNull extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
|
||||
allOf(
|
||||
@@ -43,6 +44,9 @@ public final class AssertJIsNullCheck extends BugChecker implements MethodInvoca
|
||||
argumentCount(1),
|
||||
argument(0, nullLiteral()));
|
||||
|
||||
/** Instantiates a new {@link AssertJIsNull} instance. */
|
||||
public AssertJIsNull() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
|
||||
@@ -1,11 +1,12 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -24,19 +25,22 @@ import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.List;
|
||||
|
||||
/** A {@link BugChecker} which flags redundant {@code @Autowired} constructor annotations. */
|
||||
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "AutowiredConstructor",
|
||||
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "AutowiredConstructor",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class AutowiredConstructorCheck extends BugChecker implements ClassTreeMatcher {
|
||||
public final class AutowiredConstructor extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
|
||||
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
|
||||
|
||||
/** Instantiates a new {@link AutowiredConstructor} instance. */
|
||||
public AutowiredConstructor() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
List<MethodTree> constructors = ASTHelpers.getConstructors(tree);
|
||||
@@ -1,8 +1,9 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -25,25 +26,28 @@ import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} which flags annotations that could be written more concisely. */
|
||||
/** A {@link BugChecker} that flags annotations that could be written more concisely. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "CanonicalAnnotationSyntax",
|
||||
summary = "Omit redundant syntax from annotation declarations",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "CanonicalAnnotationSyntax",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class CanonicalAnnotationSyntaxCheck extends BugChecker
|
||||
implements AnnotationTreeMatcher {
|
||||
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
|
||||
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
|
||||
FIX_FACTORIES =
|
||||
ImmutableSet.of(
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
|
||||
CanonicalAnnotationSyntax::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntax::dropRedundantCurlies);
|
||||
|
||||
/** Instantiates a new {@link CanonicalAnnotationSyntax} instance. */
|
||||
public CanonicalAnnotationSyntax() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
@@ -103,7 +107,8 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
|
||||
return Optional.of(
|
||||
SuggestedFix.replace(
|
||||
arg,
|
||||
simplifyAttributeValue(expr, state).orElseGet(() -> Util.treeToString(expr, state))));
|
||||
simplifyAttributeValue(expr, state)
|
||||
.orElseGet(() -> SourceCode.treeToString(expr, state))));
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
|
||||
@@ -138,11 +143,11 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
|
||||
private static Optional<String> simplifySingletonArray(NewArrayTree array, VisitorState state) {
|
||||
return Optional.of(array.getInitializers())
|
||||
.filter(initializers -> initializers.size() == 1)
|
||||
.map(initializers -> Util.treeToString(initializers.get(0), state));
|
||||
.map(initializers -> SourceCode.treeToString(initializers.get(0), state));
|
||||
}
|
||||
|
||||
private static Optional<String> dropTrailingComma(NewArrayTree array, VisitorState state) {
|
||||
String src = Util.treeToString(array, state);
|
||||
String src = SourceCode.treeToString(array, state);
|
||||
return Optional.of(TRAILING_ARRAY_COMMA.matcher(src))
|
||||
.filter(Matcher::find)
|
||||
.map(m -> src.substring(0, m.start()) + '}');
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -17,9 +18,10 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.stream.Collector;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@link Collector Collectors} that don't clearly express
|
||||
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
|
||||
* (im)mutability.
|
||||
*
|
||||
* <p>Replacing such collectors with alternatives that produce immutable collections is preferred.
|
||||
@@ -27,14 +29,13 @@ import java.util.stream.Collector;
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "CollectorMutability",
|
||||
summary =
|
||||
"Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "CollectorMutability",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class CollectorMutabilityCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
|
||||
staticMethod().onClass("java.util.stream.Collectors");
|
||||
@@ -45,9 +46,13 @@ public final class CollectorMutabilityCheck extends BugChecker
|
||||
private static final Matcher<ExpressionTree> SET_COLLECTOR =
|
||||
staticMethod().anyClass().named("toSet");
|
||||
|
||||
/** Instantiates a new {@link CollectorMutability} instance. */
|
||||
public CollectorMutability() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!COLLECTOR_METHOD.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)
|
||||
|| !COLLECTOR_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.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;
|
||||
@@ -20,24 +21,26 @@ import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A {@link BugChecker} which flags empty methods that seemingly can simply be deleted. */
|
||||
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "EmptyMethod",
|
||||
summary = "Empty method can likely be deleted",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "EmptyMethod",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatcher {
|
||||
public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<Tree> PERMITTED_ANNOTATION =
|
||||
annotations(
|
||||
AT_LEAST_ONE,
|
||||
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
|
||||
/** Instantiates a new {@link EmptyMethod} instance. */
|
||||
public EmptyMethod() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (tree.getBody() == null
|
||||
@@ -48,8 +51,7 @@ public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatc
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
MethodSymbol sym = ASTHelpers.getSymbol(tree);
|
||||
if (sym == null || ASTHelpers.methodCanBeOverridden(sym)) {
|
||||
if (ASTHelpers.methodCanBeOverridden(ASTHelpers.getSymbol(tree))) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -30,7 +31,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags improperly formatted Error Prone test code.
|
||||
* A {@link BugChecker} that flags improperly formatted Error Prone test code.
|
||||
*
|
||||
* <p>All test code should be formatted in accordance with Google Java Format's {@link Formatter}
|
||||
* output, and imports should be ordered according to the {@link Style#GOOGLE Google} style.
|
||||
@@ -49,12 +50,12 @@ import java.util.Optional;
|
||||
// Windows; TBD.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "ErrorProneTestHelperSourceFormat",
|
||||
summary = "Test code should follow the Google Java style",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "ErrorProneTestHelperSourceFormat",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class ErrorProneTestHelperSourceFormatCheck extends BugChecker
|
||||
public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Formatter FORMATTER = new Formatter();
|
||||
@@ -71,6 +72,9 @@ public final class ErrorProneTestHelperSourceFormatCheck extends BugChecker
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.named("addOutputLines");
|
||||
|
||||
/** Instantiates a new {@link ErrorProneTestHelperSourceFormat} instance. */
|
||||
public ErrorProneTestHelperSourceFormat() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);
|
||||
@@ -2,11 +2,12 @@ package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
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 java.util.stream.Collectors.collectingAndThen;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -30,22 +31,24 @@ import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@link Ordering#explicit(Object, Object[])}} invocations listing
|
||||
* A {@link BugChecker} that flags {@link Ordering#explicit(Object, Object[])}} invocations listing
|
||||
* a subset of an enum type's values.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "ExplicitEnumOrdering",
|
||||
summary = "Make sure `Ordering#explicit` lists all of an enum's values",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "ExplicitEnumOrdering",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class ExplicitEnumOrderingCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
|
||||
staticMethod().onClass(Ordering.class.getName()).named("explicit");
|
||||
|
||||
/** Instantiates a new {@link ExplicitEnumOrdering} instance. */
|
||||
public ExplicitEnumOrdering() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!EXPLICIT_ORDERING.matches(tree, state)) {
|
||||
@@ -73,7 +76,7 @@ public final class ExplicitEnumOrderingCheck extends BugChecker
|
||||
.collect(
|
||||
collectingAndThen(
|
||||
toImmutableSetMultimap(Symbol::asType, Symbol::toString),
|
||||
ExplicitEnumOrderingCheck::getMissingEnumValues));
|
||||
ExplicitEnumOrdering::getMissingEnumValues));
|
||||
}
|
||||
|
||||
private static ImmutableSet<String> getMissingEnumValues(
|
||||
@@ -86,6 +89,6 @@ public final class ExplicitEnumOrderingCheck extends BugChecker
|
||||
private static Stream<String> getMissingEnumValues(Type enumType, Set<String> values) {
|
||||
Symbol.TypeSymbol typeSymbol = enumType.asElement();
|
||||
return Sets.difference(ASTHelpers.enumValues(typeSymbol), values).stream()
|
||||
.map(v -> String.format("%s.%s", typeSymbol.getSimpleName(), v));
|
||||
.map(v -> String.join(".", typeSymbol.getSimpleName(), v));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -16,15 +21,18 @@ import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags usages of {@link Flux#flatMap(Function)} and {@link
|
||||
* A {@link BugChecker} that flags usages of {@link Flux#flatMap(Function)} and {@link
|
||||
* Flux#flatMapSequential(Function)}.
|
||||
*
|
||||
* <p>{@link Flux#flatMap(Function)} and {@link Flux#flatMapSequential(Function)} eagerly perform up
|
||||
@@ -32,30 +40,39 @@ import reactor.core.publisher.Flux;
|
||||
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
|
||||
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
|
||||
* avoids potentially saturating the thread pool on which subscription happens. If {@code
|
||||
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
|
||||
* {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
* concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code
|
||||
* flatMap} or {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
*
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
|
||||
* by this check because there is no clear alternative to point to.
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function,
|
||||
* java.util.function.Supplier)} is not flagged by this check because there is no clear alternative
|
||||
* to point to.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "FluxFlatMapUsage",
|
||||
summary =
|
||||
"`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; "
|
||||
+ "please use `Flux#concatMap` or explicitly specify the desired amount of concurrency",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "FluxFlatMapUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class FluxFlatMapUsageCheck extends BugChecker
|
||||
public final class FluxFlatMapUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
|
||||
private static final Supplier<Type> FLUX =
|
||||
Suppliers.typeFromString("reactor.core.publisher.Flux");
|
||||
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.onDescendantOf(FLUX)
|
||||
.namedAnyOf("flatMap", "flatMapSequential")
|
||||
.withParameters(Function.class.getName());
|
||||
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
|
||||
VisitorState.memoize(
|
||||
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
|
||||
/** Instantiates a new {@link FluxFlatMapUsage} instance. */
|
||||
public FluxFlatMapUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
@@ -63,14 +80,27 @@ public final class FluxFlatMapUsageCheck extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
|
||||
.addFix(
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build())
|
||||
.build();
|
||||
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
|
||||
SuggestedFix concurrencyCapFix =
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build();
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
|
||||
if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) {
|
||||
/*
|
||||
* Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g.
|
||||
* if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit
|
||||
* concurrently bound, in favour of sequential subscriptions using `Flux#concatMap`.
|
||||
*/
|
||||
description.addFix(concurrencyCapFix).addFix(serializationFix);
|
||||
} else {
|
||||
description.addFix(serializationFix).addFix(concurrencyCapFix);
|
||||
}
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
@@ -10,6 +10,7 @@ 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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -31,11 +32,12 @@ import com.sun.source.util.SimpleTreeVisitor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags string concatenations that produce a format string; in such
|
||||
* cases the string concatenation should instead be deferred to the invoked method.
|
||||
* A {@link BugChecker} that flags string concatenations that produce a format string; in such cases
|
||||
* the string concatenation should instead be deferred to the invoked method.
|
||||
*
|
||||
* @implNote This checker is based on the implementation of {@link
|
||||
* com.google.errorprone.bugpatterns.flogger.FloggerStringConcatenation}.
|
||||
@@ -45,16 +47,16 @@ import javax.annotation.Nullable;
|
||||
// should introduce special handling of `Formattable` arguments, as this check would replace a
|
||||
// `Formattable#toString` invocation with a `Formattable#formatTo` invocation. But likely that
|
||||
// should be considered a bug fix, too.
|
||||
// XXX: Introduce a separate check which adds/removes the `Locale` parameter to `String.format`
|
||||
// invocations, as necessary.
|
||||
// XXX: Introduce a separate check that adds/removes the `Locale` parameter to `String.format`
|
||||
// invocations, as necessary. See also a comment in the `StringJoin` check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "FormatStringConcatenation",
|
||||
summary = "Defer string concatenation to the invoked method",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "FormatStringConcatenation",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class FormatStringConcatenationCheck extends BugChecker
|
||||
public final class FormatStringConcatenation extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
@@ -127,6 +129,9 @@ public final class FormatStringConcatenationCheck extends BugChecker
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
.namedAnyOf("debug", "error", "info", "trace", "warn");
|
||||
|
||||
/** Instantiates a new {@link FormatStringConcatenation} instance. */
|
||||
public FormatStringConcatenation() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (hasNonConstantStringConcatenationArgument(tree, 0, state)) {
|
||||
@@ -202,7 +207,7 @@ public final class FormatStringConcatenationCheck extends BugChecker
|
||||
}
|
||||
|
||||
private static class ReplacementArgumentsConstructor
|
||||
extends SimpleTreeVisitor<Void, VisitorState> {
|
||||
extends SimpleTreeVisitor<@Nullable Void, VisitorState> {
|
||||
private final StringBuilder formatString = new StringBuilder();
|
||||
private final List<Tree> formatArguments = new ArrayList<>();
|
||||
private final String formatSpecifier;
|
||||
@@ -211,9 +216,8 @@ public final class FormatStringConcatenationCheck extends BugChecker
|
||||
this.formatSpecifier = formatSpecifier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitBinary(BinaryTree tree, VisitorState state) {
|
||||
public @Nullable Void visitBinary(BinaryTree tree, VisitorState state) {
|
||||
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
|
||||
tree.getLeftOperand().accept(this, state);
|
||||
tree.getRightOperand().accept(this, state);
|
||||
@@ -224,15 +228,13 @@ public final class FormatStringConcatenationCheck extends BugChecker
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
|
||||
public @Nullable Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
|
||||
return tree.getExpression().accept(this, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Void defaultAction(Tree tree, VisitorState state) {
|
||||
protected @Nullable Void defaultAction(Tree tree, VisitorState state) {
|
||||
appendExpression(tree);
|
||||
return null;
|
||||
}
|
||||
@@ -250,7 +252,7 @@ public final class FormatStringConcatenationCheck extends BugChecker
|
||||
return state.getConstantExpression(formatString.toString())
|
||||
+ ", "
|
||||
+ formatArguments.stream()
|
||||
.map(tree -> Util.treeToString(tree, state))
|
||||
.map(tree -> SourceCode.treeToString(tree, state))
|
||||
.collect(joining(", "));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static 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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.primitives.Primitives;
|
||||
@@ -27,6 +28,7 @@ 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.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
|
||||
@@ -34,13 +36,12 @@ import java.util.List;
|
||||
// the target method such a modification may change the code's semantics or performance.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "IdentityConversion",
|
||||
summary = "Avoid or clarify identity conversions",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "IdentityConversion",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class IdentityConversionCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class IdentityConversion extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
|
||||
anyOf(
|
||||
@@ -71,6 +72,9 @@ public final class IdentityConversionCheck extends BugChecker
|
||||
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
|
||||
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
|
||||
|
||||
/** Instantiates a new {@link IdentityConversion} instance. */
|
||||
public IdentityConversion() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> arguments = tree.getArguments();
|
||||
@@ -95,7 +99,7 @@ public final class IdentityConversionCheck extends BugChecker
|
||||
.setMessage(
|
||||
"This method invocation appears redundant; remove it or suppress this warning and "
|
||||
+ "add a comment explaining its purpose")
|
||||
.addFix(SuggestedFix.replace(tree, Util.treeToString(sourceTree, state)))
|
||||
.addFix(SuggestedFix.replace(tree, SourceCode.treeToString(sourceTree, state)))
|
||||
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
|
||||
.build();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
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.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.enclosingClass;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
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.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.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.sun.source.tree.MethodTree;
|
||||
import java.util.SortedSet;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link SortedSet} property declarations inside
|
||||
* {@code @Value.Immutable}- and {@code @Value.Modifiable}-annotated types that lack a
|
||||
* {@code @Value.NaturalOrder} or {@code @Value.ReverseOrder} annotation.
|
||||
*
|
||||
* <p>Without such an annotation:
|
||||
*
|
||||
* <ul>
|
||||
* <li>deserialization of the enclosing type requires that the associated JSON property is
|
||||
* present, contrary to the way in which Immutables handles other collection properties; and
|
||||
* <li>different instances may use different comparator implementations (e.g. deserialization
|
||||
* would default to natural order sorting), potentially leading to subtle bugs.
|
||||
* </ul>
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"`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,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class ImmutablesSortedSetComparator extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<MethodTree> METHOD_LACKS_ANNOTATION =
|
||||
allOf(
|
||||
methodReturns(isSubtypeOf(SortedSet.class)),
|
||||
anyOf(
|
||||
allOf(
|
||||
hasModifier(Modifier.ABSTRACT),
|
||||
enclosingClass(
|
||||
anyOf(
|
||||
hasAnnotation("org.immutables.value.Value.Immutable"),
|
||||
hasAnnotation("org.immutables.value.Value.Modifiable")))),
|
||||
hasAnnotation("org.immutables.value.Value.Default")),
|
||||
not(
|
||||
anyOf(
|
||||
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
|
||||
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
|
||||
|
||||
/** Instantiates a new {@link ImmutablesSortedSetComparator} instance. */
|
||||
public ImmutablesSortedSetComparator() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
SuggestedFix.Builder builder = SuggestedFix.builder();
|
||||
String valueTypeIdentifier =
|
||||
SuggestedFixes.qualifyType(state, builder, "org.immutables.value.Value");
|
||||
return describeMatch(
|
||||
tree,
|
||||
builder.prefixWith(tree, String.format("@%s.NaturalOrder ", valueTypeIdentifier)).build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.InstanceOfTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
|
||||
* of the form {@code T.class::isInstance}.
|
||||
*
|
||||
* @see MethodReferenceUsage
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
|
||||
link = BUG_PATTERNS_BASE_URL + "IsInstanceLambdaUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link IsInstanceLambdaUsage} instance. */
|
||||
public IsInstanceLambdaUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
if (tree.getKind() != Kind.LAMBDA_EXPRESSION || tree.getBody().getKind() != Kind.INSTANCE_OF) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(
|
||||
tree,
|
||||
SourceCode.treeToString(((InstanceOfTree) tree.getBody()).getType(), state)
|
||||
+ ".class::isInstance"));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
@@ -11,7 +11,8 @@ import static com.google.errorprone.matchers.Matchers.enclosingClass;
|
||||
import static com.google.errorprone.matchers.Matchers.hasModifier;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static tech.picnic.errorprone.bugpatterns.JavaKeywords.isReservedKeyword;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -36,21 +37,22 @@ import com.sun.tools.javac.code.Symbol;
|
||||
import java.util.Optional;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
|
||||
// XXX: Consider introducing a class-level check which enforces that test classes:
|
||||
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
|
||||
// XXX: Consider introducing a class-level check that enforces that test classes:
|
||||
// 1. Are named `*Test` or `Abstract*TestCase`.
|
||||
// 2. If not `abstract`, are package-private and don't have public methods and subclasses.
|
||||
// 3. Only have private fields.
|
||||
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "JUnitMethodDeclaration",
|
||||
summary = "JUnit method declaration can likely be improved",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "JUnitMethodDeclaration",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class JUnitMethodDeclarationCheck extends BugChecker implements MethodTreeMatcher {
|
||||
public final class JUnitMethodDeclaration extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TEST_PREFIX = "test";
|
||||
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
|
||||
@@ -77,6 +79,9 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
|
||||
isType("org.junit.jupiter.api.BeforeAll"),
|
||||
isType("org.junit.jupiter.api.BeforeEach")));
|
||||
|
||||
/** Instantiates a new {@link JUnitMethodDeclaration} instance. */
|
||||
public JUnitMethodDeclaration() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (HAS_UNMODIFIABLE_SIGNATURE.matches(tree, state)) {
|
||||
@@ -171,13 +176,12 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
|
||||
}
|
||||
|
||||
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
|
||||
String source = Util.treeToString(tree, state);
|
||||
String source = SourceCode.treeToString(tree, state);
|
||||
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
|
||||
}
|
||||
|
||||
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
|
||||
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
|
||||
.map(sym -> sym.getQualifiedName().toString())
|
||||
return Optional.of(ASTHelpers.getSymbol(tree).getQualifiedName().toString())
|
||||
.filter(name -> name.startsWith(TEST_PREFIX))
|
||||
.map(name -> name.substring(TEST_PREFIX.length()))
|
||||
.filter(not(String::isEmpty))
|
||||
@@ -1,13 +1,16 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
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 java.util.Comparator.comparing;
|
||||
import static java.util.Comparator.naturalOrder;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Comparators;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -37,22 +40,24 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags annotation array listings which aren't sorted lexicographically.
|
||||
* A {@link BugChecker} that flags annotation array listings which aren't sorted lexicographically.
|
||||
*
|
||||
* <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.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "LexicographicalAnnotationAttributeListing",
|
||||
summary = "Where possible, sort annotation array attributes lexicographically",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "LexicographicalAnnotationAttributeListing",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class LexicographicalAnnotationAttributeListingCheck extends BugChecker
|
||||
public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final ImmutableSet<String> BLACKLISTED_ANNOTATIONS =
|
||||
@@ -61,24 +66,33 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
|
||||
"com.fasterxml.jackson.annotation.JsonPropertyOrder#value",
|
||||
"io.swagger.annotations.ApiImplicitParams#value",
|
||||
"io.swagger.v3.oas.annotations.Parameters#value",
|
||||
"javax.xml.bind.annotation.XmlType#propOrder");
|
||||
"javax.xml.bind.annotation.XmlType#propOrder",
|
||||
"org.springframework.context.annotation.PropertySource#value",
|
||||
"org.springframework.test.context.TestPropertySource#locations",
|
||||
"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
|
||||
* properly sorted by key, then value.
|
||||
*/
|
||||
private static final Splitter STRING_ARGUMENT_SPLITTER = Splitter.on('=');
|
||||
|
||||
private final AnnotationAttributeMatcher matcher;
|
||||
|
||||
/** Instantiates the default {@link LexicographicalAnnotationAttributeListingCheck}. */
|
||||
public LexicographicalAnnotationAttributeListingCheck() {
|
||||
/** Instantiates a default {@link LexicographicalAnnotationAttributeListing} instance. */
|
||||
public LexicographicalAnnotationAttributeListing() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a customized {@link LexicographicalAnnotationAttributeListingCheck}.
|
||||
* Instantiates a customized {@link LexicographicalAnnotationAttributeListing}.
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
public LexicographicalAnnotationAttributeListingCheck(ErrorProneFlags flags) {
|
||||
public LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
|
||||
matcher = createAnnotationAttributeMatcher(flags);
|
||||
}
|
||||
|
||||
@@ -120,7 +134,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
|
||||
}
|
||||
|
||||
List<? extends ExpressionTree> actualOrdering = array.getInitializers();
|
||||
ImmutableList<? extends ExpressionTree> desiredOrdering = doSort(actualOrdering, state);
|
||||
ImmutableList<? extends ExpressionTree> desiredOrdering = doSort(actualOrdering);
|
||||
if (actualOrdering.equals(desiredOrdering)) {
|
||||
/* In the (presumably) common case the elements are already sorted. */
|
||||
return Optional.empty();
|
||||
@@ -129,7 +143,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
|
||||
/* The elements aren't sorted. Suggest the sorted alternative. */
|
||||
String suggestion =
|
||||
desiredOrdering.stream()
|
||||
.map(expr -> Util.treeToString(expr, state))
|
||||
.map(expr -> SourceCode.treeToString(expr, state))
|
||||
.collect(joining(", ", "{", "}"));
|
||||
return Optional.of(SuggestedFix.builder().replace(array, suggestion));
|
||||
}
|
||||
@@ -150,12 +164,12 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
|
||||
}
|
||||
|
||||
private static ImmutableList<? extends ExpressionTree> doSort(
|
||||
Iterable<? extends ExpressionTree> elements, VisitorState state) {
|
||||
Iterable<? extends ExpressionTree> elements) {
|
||||
// XXX: Perhaps we should use `Collator` with `.setStrength(Collator.PRIMARY)` and
|
||||
// `getCollationKey`. Not clear whether that's worth the hassle at this point.
|
||||
return ImmutableList.sortedCopyOf(
|
||||
comparing(
|
||||
e -> getStructure(e, state),
|
||||
LexicographicalAnnotationAttributeListing::getStructure,
|
||||
Comparators.lexicographical(
|
||||
Comparators.lexicographical(
|
||||
String.CASE_INSENSITIVE_ORDER.thenComparing(naturalOrder())))),
|
||||
@@ -167,38 +181,31 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
|
||||
* performed. This approach disregards e.g. irrelevant whitespace. It also allows special
|
||||
* structure within string literals to be respected.
|
||||
*/
|
||||
private static ImmutableList<ImmutableList<String>> getStructure(
|
||||
ExpressionTree array, VisitorState state) {
|
||||
private static ImmutableList<ImmutableList<String>> getStructure(ExpressionTree array) {
|
||||
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
|
||||
|
||||
new TreeScanner<Void, Void>() {
|
||||
@Nullable
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree node, Void ctx) {
|
||||
nodes.add(tokenize(node));
|
||||
return super.visitIdentifier(node, ctx);
|
||||
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getName().toString()));
|
||||
return super.visitIdentifier(node, unused);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitLiteral(LiteralTree node, Void ctx) {
|
||||
nodes.add(tokenize(node));
|
||||
return super.visitLiteral(node, ctx);
|
||||
public @Nullable Void visitLiteral(LiteralTree node, @Nullable Void unused) {
|
||||
Object value = ASTHelpers.constValue(node);
|
||||
nodes.add(
|
||||
value instanceof String
|
||||
? STRING_ARGUMENT_SPLITTER.splitToStream((String) value).collect(toImmutableList())
|
||||
: ImmutableList.of(String.valueOf(value)));
|
||||
|
||||
return super.visitLiteral(node, unused);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitPrimitiveType(PrimitiveTypeTree node, Void ctx) {
|
||||
nodes.add(tokenize(node));
|
||||
return super.visitPrimitiveType(node, ctx);
|
||||
}
|
||||
|
||||
private ImmutableList<String> tokenize(Tree node) {
|
||||
/*
|
||||
* Tokens are split on `=` so that e.g. inline Spring property declarations are properly
|
||||
* sorted by key, then value.
|
||||
*/
|
||||
return ImmutableList.copyOf(Util.treeToString(node, state).split("=", -1));
|
||||
public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString()));
|
||||
return super.visitPrimitiveType(node, unused);
|
||||
}
|
||||
}.scan(array, null);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -16,10 +20,15 @@ import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.TypeAnnotations.AnnotationType;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags annotations that are not lexicographically sorted.
|
||||
@@ -29,14 +38,20 @@ import java.util.Optional;
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "LexicographicalAnnotationListing",
|
||||
summary = "Sort annotations lexicographically where possible",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "LexicographicalAnnotationListing",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class LexicographicalAnnotationListingCheck extends BugChecker
|
||||
public final class LexicographicalAnnotationListing extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Comparator<@Nullable AnnotationType> BY_ANNOTATION_TYPE =
|
||||
(a, b) ->
|
||||
(a == null || a == DECLARATION) && b == TYPE ? -1 : a == TYPE && b == DECLARATION ? 1 : 0;
|
||||
|
||||
/** Instantiates a new {@link LexicographicalAnnotationListing} instance. */
|
||||
public LexicographicalAnnotationListing() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
@@ -45,26 +60,29 @@ public final class LexicographicalAnnotationListingCheck extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations = sort(originalOrdering, state);
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations =
|
||||
sort(originalOrdering, ASTHelpers.getSymbol(tree), state);
|
||||
if (originalOrdering.equals(sortedAnnotations)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
|
||||
|
||||
Description.Builder description = buildDescription(originalOrdering.get(0));
|
||||
fix.ifPresent(description::addFix);
|
||||
return description.build();
|
||||
return describeMatch(
|
||||
originalOrdering.get(0), fixOrdering(originalOrdering, sortedAnnotations, state));
|
||||
}
|
||||
|
||||
private static ImmutableList<? extends AnnotationTree> sort(
|
||||
List<? extends AnnotationTree> annotations, VisitorState state) {
|
||||
List<? extends AnnotationTree> annotations, Symbol symbol, VisitorState state) {
|
||||
return annotations.stream()
|
||||
.sorted(comparing(annotation -> Util.treeToString(annotation, state)))
|
||||
.sorted(
|
||||
comparing(
|
||||
(AnnotationTree annotation) ->
|
||||
ASTHelpers.getAnnotationType(annotation, symbol, state),
|
||||
BY_ANNOTATION_TYPE)
|
||||
.thenComparing(annotation -> SourceCode.treeToString(annotation, state)))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static Optional<Fix> tryFixOrdering(
|
||||
private static Fix fixOrdering(
|
||||
List<? extends AnnotationTree> originalAnnotations,
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations,
|
||||
VisitorState state) {
|
||||
@@ -72,8 +90,10 @@ public final class LexicographicalAnnotationListingCheck extends BugChecker
|
||||
originalAnnotations.stream(),
|
||||
sortedAnnotations.stream(),
|
||||
(original, replacement) ->
|
||||
SuggestedFix.builder().replace(original, Util.treeToString(replacement, state)))
|
||||
SuggestedFix.builder()
|
||||
.replace(original, SourceCode.treeToString(replacement, state)))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build);
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.orElseThrow(() -> new VerifyException("No annotations were provided"));
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -35,7 +36,9 @@ import java.util.Optional;
|
||||
import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags lambda expressions that can be replaced with method references.
|
||||
* 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.
|
||||
@@ -49,17 +52,22 @@ import javax.lang.model.element.Name;
|
||||
// `not(some::reference)`.
|
||||
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
|
||||
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
|
||||
// XXX: Expressions of the form `i -> SomeType.class.isInstance(i)` are not replaced; fix that using
|
||||
// a suitable generalization.
|
||||
// XXX: Consider folding the `IsInstanceLambdaUsage` check into this class.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "MethodReferenceUsage",
|
||||
summary = "Prefer method references over lambda expressions",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "MethodReferenceUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class MethodReferenceUsageCheck extends BugChecker
|
||||
implements LambdaExpressionTreeMatcher {
|
||||
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link MethodReferenceUsage} instance. */
|
||||
public MethodReferenceUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
/*
|
||||
@@ -102,6 +110,8 @@ public final class MethodReferenceUsageCheck extends BugChecker
|
||||
.flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0)));
|
||||
}
|
||||
|
||||
// XXX: Replace nested `Optional` usage.
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
private static Optional<SuggestedFix.Builder> constructMethodRef(
|
||||
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
|
||||
return matchArguments(lambdaExpr, subTree)
|
||||
@@ -120,7 +130,7 @@ public final class MethodReferenceUsageCheck extends BugChecker
|
||||
return Optional.empty();
|
||||
}
|
||||
Symbol sym = ASTHelpers.getSymbol(methodSelect);
|
||||
if (!sym.isStatic()) {
|
||||
if (!ASTHelpers.isStatic(sym)) {
|
||||
return constructFix(lambdaExpr, "this", methodSelect);
|
||||
}
|
||||
return constructFix(lambdaExpr, sym.owner, methodSelect);
|
||||
@@ -158,6 +168,8 @@ public final class MethodReferenceUsageCheck extends BugChecker
|
||||
return constructFix(lambdaExpr, lhsType.tsym, subTree.getIdentifier());
|
||||
}
|
||||
|
||||
// XXX: Refactor or replace inner `Optional` with a custom type.
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
private static Optional<Optional<Name>> matchArguments(
|
||||
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
|
||||
ImmutableList<Name> expectedArguments = getVariables(lambdaExpr);
|
||||
@@ -188,7 +200,7 @@ public final class MethodReferenceUsageCheck extends BugChecker
|
||||
Name sName = target.getSimpleName();
|
||||
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
|
||||
|
||||
if (!"java.lang".equals(target.packge().toString())) {
|
||||
if (!"java.lang".equals(ASTHelpers.enclosingPackage(target).toString())) {
|
||||
Name fqName = target.getQualifiedName();
|
||||
if (!sName.equals(fqName)) {
|
||||
return fix.map(b -> b.addImport(fqName.toString()));
|
||||
@@ -1,12 +1,13 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.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;
|
||||
@@ -24,12 +25,12 @@ import com.sun.source.tree.Tree;
|
||||
/** A {@link BugChecker} that flags likely missing Refaster annotations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "MissingRefasterAnnotation",
|
||||
summary = "The Refaster template contains a method without any Refaster annotations",
|
||||
linkType = NONE,
|
||||
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 MissingRefasterAnnotationCheck extends BugChecker implements ClassTreeMatcher {
|
||||
public final class MissingRefasterAnnotation extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<Tree, AnnotationTree> REFASTER_ANNOTATION =
|
||||
annotations(
|
||||
@@ -39,6 +40,9 @@ public final class MissingRefasterAnnotationCheck extends BugChecker implements
|
||||
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 =
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -17,23 +18,27 @@ 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.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags method invocations for which all arguments are wrapped using
|
||||
* A {@link BugChecker} that flags method invocations for which all arguments are wrapped using
|
||||
* {@link org.mockito.Mockito#eq}; this is redundant.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "MockitoStubbing",
|
||||
summary = "Don't unnecessarily use Mockito's `eq(...)`",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "MockitoStubbing",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class MockitoStubbingCheck extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
public final class MockitoStubbing extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
|
||||
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
|
||||
|
||||
/** Instantiates a new {@link MockitoStubbing} instance. */
|
||||
public MockitoStubbing() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> arguments = tree.getArguments();
|
||||
@@ -45,7 +50,7 @@ public final class MockitoStubbingCheck extends BugChecker implements MethodInvo
|
||||
for (ExpressionTree arg : arguments) {
|
||||
suggestedFix.replace(
|
||||
arg,
|
||||
Util.treeToString(
|
||||
SourceCode.treeToString(
|
||||
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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.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;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A {@link BugChecker} that flags nesting of {@link Optional Optionals}. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"Avoid nesting `Optional`s inside `Optional`s; the resultant code is hard to reason about",
|
||||
link = BUG_PATTERNS_BASE_URL + "NestedOptionals",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
|
||||
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
|
||||
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
|
||||
|
||||
/** Instantiates a new {@link NestedOptionals} instance. */
|
||||
public NestedOptionals() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
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.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.function.BiFunction;
|
||||
import reactor.core.publisher.Mono;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Mono} operations that are known to be vacuous, given that
|
||||
* they are invoked on a {@link Mono} that is known not to complete empty.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid vacuous operations on known non-empty `Mono`s",
|
||||
link = BUG_PATTERNS_BASE_URL + "NonEmptyMono",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
// XXX: This check does not simplify `someFlux.defaultIfEmpty(T).{defaultIfEmpty(T),hasElements()}`,
|
||||
// as `someFlux.defaultIfEmpty(T)` yields a `Flux` rather than a `Mono`. Consider adding support for
|
||||
// these cases.
|
||||
// XXX: Given more advanced analysis many more expressions could be flagged. Consider
|
||||
// `Mono.just(someValue)`, `Flux.just(someNonEmptySequence)`,
|
||||
// `someMono.switchIfEmpty(someProvablyNonEmptyMono)` and many other variants.
|
||||
// XXX: Consider implementing a similar check for `Publisher`s that are known to complete without
|
||||
// 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.
|
||||
public final class NonEmptyMono extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "single", "switchIfEmpty");
|
||||
private static final Matcher<ExpressionTree> NON_EMPTY_MONO =
|
||||
anyOf(
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.namedAnyOf(
|
||||
"all",
|
||||
"any",
|
||||
"collect",
|
||||
"collectList",
|
||||
"collectMap",
|
||||
"collectMultimap",
|
||||
"collectSortedList",
|
||||
"count",
|
||||
"elementAt",
|
||||
"hasElement",
|
||||
"hasElements",
|
||||
"last",
|
||||
"reduceWith",
|
||||
"single"),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.named("reduce")
|
||||
.withParameters(Object.class.getName(), BiFunction.class.getName()),
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
|
||||
|
||||
/** Instantiates a new {@link NonEmptyMono} instance. */
|
||||
public NonEmptyMono() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!MONO_SIZE_CHECK.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (!NON_EMPTY_MONO.matches(receiver, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree, SuggestedFix.replace(tree, SourceCode.treeToString(receiver, state)));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -32,24 +32,24 @@ import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@code Comparator#comparing*} invocations that can be replaced
|
||||
* A {@link BugChecker} that flags {@code Comparator#comparing*} invocations that can be replaced
|
||||
* with an equivalent alternative so as to avoid unnecessary (un)boxing.
|
||||
*/
|
||||
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more
|
||||
// specific types.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "PrimitiveComparison",
|
||||
summary =
|
||||
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
|
||||
+ " of the provided function",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "PrimitiveComparison",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = PERFORMANCE)
|
||||
public final class PrimitiveComparisonCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class PrimitiveComparison extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
|
||||
anyOf(
|
||||
@@ -70,6 +70,9 @@ public final class PrimitiveComparisonCheck extends BugChecker
|
||||
.named("thenComparing")
|
||||
.withParameters(Function.class.getName()));
|
||||
|
||||
/** Instantiates a new {@link PrimitiveComparison} instance. */
|
||||
public PrimitiveComparison() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isStatic = STATIC_COMPARISON_METHOD.matches(tree, state);
|
||||
@@ -85,16 +88,15 @@ public final class PrimitiveComparisonCheck extends BugChecker
|
||||
|
||||
private static Optional<Fix> attemptMethodInvocationReplacement(
|
||||
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
|
||||
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
|
||||
.map(methodSymbol -> methodSymbol.getSimpleName().toString())
|
||||
.flatMap(
|
||||
actualMethodName ->
|
||||
Optional.of(getPreferredMethod(cmpType, isStatic, state))
|
||||
.filter(not(actualMethodName::equals)))
|
||||
.map(
|
||||
preferredMethodName ->
|
||||
prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state))
|
||||
.map(preferredMethodName -> suggestFix(tree, preferredMethodName, state));
|
||||
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
|
||||
String preferredMethodName = getPreferredMethod(cmpType, isStatic, state);
|
||||
if (actualMethodName.equals(preferredMethodName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(
|
||||
suggestFix(
|
||||
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +118,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
|
||||
|
||||
String typeArguments =
|
||||
Stream.concat(
|
||||
Stream.of(Util.treeToString(tree.getTypeArguments().get(0), state)),
|
||||
Stream.of(SourceCode.treeToString(tree.getTypeArguments().get(0), state)),
|
||||
Stream.of(cmpType.tsym.getSimpleName())
|
||||
.filter(u -> "comparing".equals(preferredMethodName)))
|
||||
.collect(joining(", ", "<", ">"));
|
||||
@@ -171,7 +173,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
|
||||
case MEMBER_SELECT:
|
||||
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
|
||||
return SuggestedFix.replace(
|
||||
ms, Util.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
|
||||
ms, SourceCode.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.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.argumentCount;
|
||||
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
|
||||
import static com.google.errorprone.matchers.Matchers.isSameType;
|
||||
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.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.common.primitives.Primitives;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
@@ -24,6 +30,7 @@ import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.source.tree.BinaryTree;
|
||||
import com.sun.source.tree.CompoundAssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
@@ -41,16 +48,19 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} which flags redundant explicit string conversions. */
|
||||
/** A {@link BugChecker} that flags redundant explicit string conversions. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "RedundantStringConversion",
|
||||
summary = "Avoid redundant string conversions when possible",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "RedundantStringConversion",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class RedundantStringConversionCheck extends BugChecker
|
||||
public final class RedundantStringConversion extends BugChecker
|
||||
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String FLAG_PREFIX = "RedundantStringConversion:";
|
||||
@@ -68,49 +78,30 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
allOf(STRING, isNonNullUsingDataflow());
|
||||
private static final Matcher<ExpressionTree> NOT_FORMATTABLE =
|
||||
not(isSubtypeOf(Formattable.class));
|
||||
private static final Matcher<ExpressionTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
|
||||
private static final Matcher<MethodInvocationTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
|
||||
anyOf(
|
||||
instanceMethod().onDescendantOfAny(Object.class.getName()).named("toString"),
|
||||
staticMethod()
|
||||
.onClass(Objects.class.getName())
|
||||
instanceMethod()
|
||||
.onDescendantOfAny(Object.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(Object.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(String.class.getName())
|
||||
.named("valueOf")
|
||||
.withParameters(Object.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(String.class.getName())
|
||||
.named("valueOf")
|
||||
.withParameters(String.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Byte.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(byte.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Character.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(char.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Short.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(short.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Integer.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(int.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Long.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(long.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Float.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(float.class.getName()),
|
||||
staticMethod()
|
||||
.onClass(Double.class.getName())
|
||||
.named("toString")
|
||||
.withParameters(double.class.getName()));
|
||||
.withNoParameters(),
|
||||
allOf(
|
||||
argumentCount(1),
|
||||
anyOf(
|
||||
staticMethod()
|
||||
.onClassAny(
|
||||
Stream.concat(
|
||||
Primitives.allWrapperTypes().stream(), Stream.of(Objects.class))
|
||||
.map(Class::getName)
|
||||
.collect(toImmutableSet()))
|
||||
.named("toString"),
|
||||
allOf(
|
||||
staticMethod().onClass(String.class.getName()).named("valueOf"),
|
||||
not(
|
||||
anyMethod()
|
||||
.anyClass()
|
||||
.withAnyName()
|
||||
.withParametersOfType(
|
||||
ImmutableList.of(Suppliers.arrayOf(Suppliers.CHAR_TYPE))))))));
|
||||
private static final Matcher<ExpressionTree> STRINGBUILDER_APPEND_INVOCATION =
|
||||
instanceMethod()
|
||||
.onDescendantOf(StringBuilder.class.getName())
|
||||
@@ -126,17 +117,10 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
staticMethod().onClass(String.class.getName()).named("format"),
|
||||
instanceMethod().onDescendantOf(Formatter.class.getName()).named("format"),
|
||||
instanceMethod()
|
||||
.onDescendantOf(PrintStream.class.getName())
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.namedAnyOf("format", "printf"),
|
||||
instanceMethod()
|
||||
.onDescendantOf(PrintStream.class.getName())
|
||||
.namedAnyOf("print", "println")
|
||||
.withParameters(Object.class.getName()),
|
||||
instanceMethod()
|
||||
.onDescendantOf(PrintWriter.class.getName())
|
||||
.namedAnyOf("format", "printf"),
|
||||
instanceMethod()
|
||||
.onDescendantOf(PrintWriter.class.getName())
|
||||
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
|
||||
.namedAnyOf("print", "println")
|
||||
.withParameters(Object.class.getName()),
|
||||
staticMethod()
|
||||
@@ -155,19 +139,19 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
.namedAnyOf("trace", "debug", "info", "warn", "error");
|
||||
|
||||
private final Matcher<ExpressionTree> conversionMethodMatcher;
|
||||
private final Matcher<MethodInvocationTree> conversionMethodMatcher;
|
||||
|
||||
/** Instantiates the default {@link RedundantStringConversionCheck}. */
|
||||
public RedundantStringConversionCheck() {
|
||||
/** Instantiates a default {@link RedundantStringConversion} instance. */
|
||||
public RedundantStringConversion() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a customized {@link RedundantStringConversionCheck}.
|
||||
* Instantiates a customized {@link RedundantStringConversion}.
|
||||
*
|
||||
* @param flags Any provided command line flags.
|
||||
*/
|
||||
public RedundantStringConversionCheck(ErrorProneFlags flags) {
|
||||
public RedundantStringConversion(ErrorProneFlags flags) {
|
||||
conversionMethodMatcher = createConversionMethodMatcher(flags);
|
||||
}
|
||||
|
||||
@@ -185,7 +169,7 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
|
||||
// XXX: Not so nice: we try to simplify the RHS twice.
|
||||
// XXX: Avoid trying to simplify the RHS twice.
|
||||
ExpressionTree preferredRhs = trySimplify(rhs, state).orElse(rhs);
|
||||
if (STRING.matches(preferredRhs, state)) {
|
||||
tryFix(lhs, state, ANY_EXPR).ifPresent(fixes::add);
|
||||
@@ -242,7 +226,7 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
.flatMap(args -> tryFix(args.get(index), state, ANY_EXPR));
|
||||
}
|
||||
|
||||
// XXX: Write another check which checks that Formatter patterns don't use `{}` and have a
|
||||
// XXX: Write another check that checks that Formatter patterns don't use `{}` and have a
|
||||
// matching number of arguments of the appropriate type. Also flag explicit conversions from
|
||||
// `Formattable` to string.
|
||||
private Optional<SuggestedFix.Builder> tryFixFormatter(
|
||||
@@ -273,17 +257,17 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
return tryFixFormatterArguments(arguments, state, ANY_EXPR, ANY_EXPR);
|
||||
}
|
||||
|
||||
// XXX: Write another check which checks that SLF4J patterns don't use `%s` and have a matching
|
||||
// XXX: Write another check that checks that SLF4J patterns don't use `%s` and have a matching
|
||||
// number of arguments of the appropriate type. Also flag explicit conversions from `Throwable` to
|
||||
// string as the last logger argument. Suggests either dropping the converison or going with
|
||||
// string as the last logger argument. Suggests either dropping the conversion or going with
|
||||
// `Throwable#getMessage()` instead.
|
||||
private Optional<SuggestedFix.Builder> tryFixSlf4jLogger(
|
||||
List<? extends ExpressionTree> arguments, VisitorState state) {
|
||||
/*
|
||||
* SLF4J treats the final argument to a log statement specially if it is a `Throwabe`: it
|
||||
* SLF4J treats the final argument to a log statement specially if it is a `Throwable`: it
|
||||
* will always choose to render the associated stacktrace, even if the argument has a
|
||||
* matching `{}` placeholder. (In this case the `{}` will simply be logged verbatim.) So if
|
||||
* a log statement's final argument is the string representation of a `Throwble`, then we
|
||||
* a log statement's final argument is the string representation of a `Throwable`, then we
|
||||
* must not strip this explicit string conversion, as that would change the statement's
|
||||
* semantics.
|
||||
*/
|
||||
@@ -326,7 +310,7 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
return trySimplify(tree, state, filter)
|
||||
.map(
|
||||
replacement ->
|
||||
SuggestedFix.builder().replace(tree, Util.treeToString(replacement, state)));
|
||||
SuggestedFix.builder().replace(tree, SourceCode.treeToString(replacement, state)));
|
||||
}
|
||||
|
||||
private Optional<ExpressionTree> trySimplify(
|
||||
@@ -337,11 +321,15 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
}
|
||||
|
||||
private Optional<ExpressionTree> trySimplify(ExpressionTree tree, VisitorState state) {
|
||||
if (tree.getKind() != Kind.METHOD_INVOCATION || !conversionMethodMatcher.matches(tree, state)) {
|
||||
if (tree.getKind() != Kind.METHOD_INVOCATION) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
|
||||
if (!conversionMethodMatcher.matches(methodInvocation, state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
switch (methodInvocation.getArguments().size()) {
|
||||
case 0:
|
||||
return trySimplifyNullaryMethod(methodInvocation, state);
|
||||
@@ -350,7 +338,7 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Cannot simplify method call with two or more arguments: "
|
||||
+ Util.treeToString(tree, state));
|
||||
+ SourceCode.treeToString(tree, state));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +351,7 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
return Optional.of(methodInvocation.getMethodSelect())
|
||||
.filter(methodSelect -> methodSelect.getKind() == Kind.MEMBER_SELECT)
|
||||
.map(methodSelect -> ((MemberSelectTree) methodSelect).getExpression())
|
||||
.filter(expr -> !"super".equals(Util.treeToString(expr, state)));
|
||||
.filter(expr -> !"super".equals(SourceCode.treeToString(expr, state)));
|
||||
}
|
||||
|
||||
private static Optional<ExpressionTree> trySimplifyUnaryMethod(
|
||||
@@ -382,7 +370,8 @@ public final class RedundantStringConversionCheck extends BugChecker
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> createConversionMethodMatcher(ErrorProneFlags flags) {
|
||||
private static Matcher<MethodInvocationTree> createConversionMethodMatcher(
|
||||
ErrorProneFlags flags) {
|
||||
// XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas.
|
||||
// For this class methods accepting more than one argument are not valid, but still: not nice.
|
||||
return flags
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -16,26 +17,29 @@ 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.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags unnecessary {@link Refaster#anyOf(Object[])} usages.
|
||||
* A {@link BugChecker} that flags unnecessary {@link Refaster#anyOf(Object[])} usages.
|
||||
*
|
||||
* <p>Note that this logic can't be implemented as a Refaster template, as the {@link Refaster}
|
||||
* class is treated specially.
|
||||
* <p>Note that this logic can't be implemented as a Refaster rule, as the {@link Refaster} class is
|
||||
* treated specially.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "RefasterAnyOfUsage",
|
||||
summary = "`Refaster#anyOf` should be passed at least two parameters",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "RefasterAnyOfUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class RefasterAnyOfUsageCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
|
||||
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
|
||||
|
||||
/** Instantiates a new {@link RefasterAnyOfUsage} instance. */
|
||||
public RefasterAnyOfUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (REFASTER_ANY_OF.matches(tree, state)) {
|
||||
@@ -46,7 +50,8 @@ public final class RefasterAnyOfUsageCheck extends BugChecker
|
||||
case 1:
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(tree, Util.treeToString(tree.getArguments().get(0), state)));
|
||||
SuggestedFix.replace(
|
||||
tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
|
||||
default:
|
||||
/* Handled below. */
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
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.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.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.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that suggests a canonical set of modifiers for Refaster class and method
|
||||
* definitions.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Refaster class and method definitions should specify a canonical set of modifiers",
|
||||
link = BUG_PATTERNS_BASE_URL + "RefasterRuleModifiers",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = STYLE)
|
||||
public final class RefasterRuleModifiers extends BugChecker
|
||||
implements ClassTreeMatcher, MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<Tree> BEFORE_TEMPLATE_METHOD = hasAnnotation(BeforeTemplate.class);
|
||||
private static final Matcher<Tree> AFTER_TEMPLATE_METHOD = hasAnnotation(AfterTemplate.class);
|
||||
private static final Matcher<Tree> PLACEHOLDER_METHOD = hasAnnotation(Placeholder.class);
|
||||
private static final Matcher<Tree> REFASTER_METHOD =
|
||||
anyOf(BEFORE_TEMPLATE_METHOD, AFTER_TEMPLATE_METHOD, PLACEHOLDER_METHOD);
|
||||
|
||||
/** Instantiates a new {@link RefasterRuleModifiers} instance. */
|
||||
public RefasterRuleModifiers() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
if (!hasMatchingMember(tree, BEFORE_TEMPLATE_METHOD, state)) {
|
||||
/* This class does not contain a Refaster template. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
SuggestedFix fix = suggestCanonicalModifiers(tree, state);
|
||||
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!REFASTER_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return SuggestedFixes.removeModifiers(
|
||||
tree,
|
||||
state,
|
||||
Modifier.FINAL,
|
||||
Modifier.PRIVATE,
|
||||
Modifier.PROTECTED,
|
||||
Modifier.PUBLIC,
|
||||
Modifier.STATIC,
|
||||
Modifier.SYNCHRONIZED)
|
||||
.map(fix -> describeMatch(tree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static SuggestedFix suggestCanonicalModifiers(ClassTree tree, VisitorState state) {
|
||||
Set<Modifier> modifiersToAdd = EnumSet.noneOf(Modifier.class);
|
||||
Set<Modifier> modifiersToRemove =
|
||||
EnumSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC, Modifier.SYNCHRONIZED);
|
||||
|
||||
if (!hasMatchingMember(tree, PLACEHOLDER_METHOD, state)) {
|
||||
/*
|
||||
* Rules without a `@Placeholder` method should be `final`. Note that Refaster enforces
|
||||
* that `@Placeholder` methods are `abstract`, so rules _with_ such a method will
|
||||
* naturally be `abstract` and non-`final`.
|
||||
*/
|
||||
modifiersToAdd.add(Modifier.FINAL);
|
||||
modifiersToRemove.add(Modifier.ABSTRACT);
|
||||
}
|
||||
|
||||
if (ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class) != null) {
|
||||
/* Nested classes should be `static`. */
|
||||
modifiersToAdd.add(Modifier.STATIC);
|
||||
}
|
||||
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
SuggestedFixes.addModifiers(tree, tree.getModifiers(), state, modifiersToAdd)
|
||||
.ifPresent(fix::merge);
|
||||
SuggestedFixes.removeModifiers(tree.getModifiers(), state, modifiersToRemove)
|
||||
.ifPresent(fix::merge);
|
||||
return fix.build();
|
||||
}
|
||||
|
||||
private static boolean hasMatchingMember(
|
||||
ClassTree tree, Matcher<Tree> matcher, VisitorState state) {
|
||||
return tree.getMembers().stream().anyMatch(member -> matcher.matches(member, state));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
|
||||
@@ -11,6 +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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -23,19 +24,19 @@ import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@code @RequestMapping} methods that have one or more parameters
|
||||
* A {@link BugChecker} that flags {@code @RequestMapping} methods that have one or more parameters
|
||||
* that appear to lack a relevant annotation.
|
||||
*
|
||||
* <p>Matched mappings are {@code @{Delete,Get,Patch,Post,Put,Request}Mapping}.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "RequestMappingAnnotation",
|
||||
summary = "Make sure all `@RequestMapping` method parameters are annotated",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "RequestMappingAnnotation",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class RequestMappingAnnotationCheck extends BugChecker implements MethodTreeMatcher {
|
||||
public final class RequestMappingAnnotation extends BugChecker implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
|
||||
// XXX: Generalize this logic to fully support Spring meta-annotations, then update the class
|
||||
@@ -64,9 +65,11 @@ public final class RequestMappingAnnotationCheck extends BugChecker implements M
|
||||
AT_LEAST_ONE,
|
||||
anyOf(
|
||||
isType(ANN_PACKAGE_PREFIX + "PathVariable"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestParam"))),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
|
||||
isType(ANN_PACKAGE_PREFIX + "RequestPart"))),
|
||||
isSameType("java.io.InputStream"),
|
||||
isSameType("java.time.ZoneId"),
|
||||
isSameType("java.util.Locale"),
|
||||
@@ -80,6 +83,9 @@ public final class RequestMappingAnnotationCheck extends BugChecker implements M
|
||||
isSameType("org.springframework.web.util.UriBuilder"),
|
||||
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
|
||||
|
||||
/** Instantiates a new {@link RequestMappingAnnotation} instance. */
|
||||
public RequestMappingAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
// XXX: Auto-add `@RequestParam` where applicable.
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
@@ -9,6 +9,7 @@ import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
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 tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
@@ -21,21 +22,24 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
|
||||
/** A {@link BugChecker} which flags {@code @RequestParam} parameters with an unsupported type. */
|
||||
/** A {@link BugChecker} that flags {@code @RequestParam} parameters with an unsupported type. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "RequestParamType",
|
||||
summary = "`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "RequestParamType",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class RequestParamTypeCheck extends BugChecker implements VariableTreeMatcher {
|
||||
public final class RequestParamType extends BugChecker implements VariableTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<VariableTree> HAS_UNSUPPORTED_REQUEST_PARAM =
|
||||
allOf(
|
||||
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
|
||||
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)));
|
||||
|
||||
/** Instantiates a new {@link RequestParamType} instance. */
|
||||
public RequestParamType() {}
|
||||
|
||||
@Override
|
||||
public Description matchVariable(VariableTree tree, VisitorState state) {
|
||||
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)
|
||||
@@ -1,12 +1,13 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.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;
|
||||
@@ -25,19 +26,20 @@ 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} which flags methods with Spring's {@code @Scheduled} annotation that lack
|
||||
* New Relic Agent's {@code @Trace(dispatcher = true)}.
|
||||
* 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(
|
||||
name = "ScheduledTransactionTrace",
|
||||
summary = "Scheduled operation must start a new New Relic transaction",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "ScheduledTransactionTrace",
|
||||
linkType = CUSTOM,
|
||||
severity = ERROR,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class ScheduledTransactionTraceCheck extends BugChecker implements MethodTreeMatcher {
|
||||
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 =
|
||||
@@ -45,9 +47,13 @@ public final class ScheduledTransactionTraceCheck extends BugChecker implements
|
||||
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 (!IS_SCHEDULED.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|
||||
|| !IS_SCHEDULED.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Scope;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags annotations with time attributes that can be written more
|
||||
* concisely.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Simplifies annotations which express an amount of time using a `TimeUnit`",
|
||||
link = BUG_PATTERNS_BASE_URL + "SimplifyTimeAnnotation",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class SimplifyTimeAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
|
||||
createAnnotationAttributeMatcher();
|
||||
|
||||
/** Instantiates a new {@link SimplifyTimeAnnotationCheck} instance. */
|
||||
public SimplifyTimeAnnotationCheck() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState state) {
|
||||
ImmutableList<ExpressionTree> arguments =
|
||||
ARGUMENT_SELECTOR.extractMatchingArguments(annotationTree).collect(toImmutableList());
|
||||
|
||||
if (arguments.isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return trySimplification(annotationTree, arguments, state)
|
||||
.map(fix -> describeMatch(annotationTree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> trySimplification(
|
||||
AnnotationTree annotation, ImmutableList<ExpressionTree> arguments, VisitorState state) {
|
||||
checkArgument(!arguments.isEmpty());
|
||||
|
||||
AnnotationDescriptor annotationDescriptor =
|
||||
AnnotationDescriptor.from(getAnnotationFqcn(annotation));
|
||||
if (containsAnyAttributeOf(annotation, annotationDescriptor.bannedFields)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ImmutableMap<String, ExpressionTree> indexedAttributes =
|
||||
Maps.uniqueIndex(
|
||||
arguments,
|
||||
expr ->
|
||||
ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
|
||||
.getSimpleName()
|
||||
.toString());
|
||||
|
||||
TimeUnit currentTimeUnit =
|
||||
getTimeUnit(annotation, annotationDescriptor.timeUnitField, indexedAttributes);
|
||||
|
||||
ImmutableMap<String, Number> timeValues =
|
||||
annotationDescriptor.timeFields.stream()
|
||||
.map(field -> Map.entry(field, getValue(field, indexedAttributes)))
|
||||
.filter(entry -> entry.getValue().isPresent())
|
||||
.collect(toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().orElseThrow()));
|
||||
|
||||
Map<String, TimeSimplifier.Simplification> simplifications =
|
||||
Maps.transformValues(
|
||||
Maps.filterValues(
|
||||
Maps.transformEntries(
|
||||
timeValues, (field, value) -> trySimplify(value, currentTimeUnit)),
|
||||
Optional::isPresent),
|
||||
Optional::orElseThrow);
|
||||
|
||||
// Some could not be simplified, and since the unit is shared, the others can't either.
|
||||
if (simplifications.size() != timeValues.size()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// The annotation is of the form `@Annotation(v)` or `@Annotation(value = v)`. For the former we
|
||||
// must synthesize the entire annotation, but this is OK for the latter, too.
|
||||
if (indexedAttributes.size() == 1 && simplifications.containsKey("value")) {
|
||||
TimeSimplifier.Simplification simplification = simplifications.get("value");
|
||||
return Optional.of(
|
||||
getImplicitValueAttributeFix(
|
||||
annotation,
|
||||
simplification.value,
|
||||
annotationDescriptor.timeUnitField,
|
||||
simplification.timeUnit,
|
||||
state));
|
||||
}
|
||||
|
||||
// Since each might have a different simplification possible, check the common unit.
|
||||
// Since we only get simplifications iff it's possible, and we check that all can be simplified,
|
||||
// we don't need to check if this equals `currentTimeUnit`.
|
||||
TimeUnit commonUnit =
|
||||
findCommonUnit(
|
||||
ImmutableSet.copyOf(
|
||||
Maps.transformValues(simplifications, simplification -> simplification.timeUnit)
|
||||
.values()));
|
||||
|
||||
return getExplicitAttributesFix(
|
||||
annotation, simplifications, annotationDescriptor.timeUnitField, commonUnit, state);
|
||||
}
|
||||
|
||||
private static boolean containsAnyAttributeOf(
|
||||
AnnotationTree annotation, ImmutableSet<String> attributes) {
|
||||
return annotation.getArguments().stream()
|
||||
.map(
|
||||
expr ->
|
||||
expr.getKind() == Tree.Kind.ASSIGNMENT
|
||||
? ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
|
||||
.getSimpleName()
|
||||
.toString()
|
||||
: "value")
|
||||
.anyMatch(attributes::contains);
|
||||
}
|
||||
|
||||
private static Fix getImplicitValueAttributeFix(
|
||||
AnnotationTree annotation,
|
||||
long newValue,
|
||||
String timeUnitField,
|
||||
TimeUnit newTimeUnit,
|
||||
VisitorState state) {
|
||||
String synthesizedAnnotation =
|
||||
SourceCode.treeToString(annotation, state)
|
||||
.replaceFirst(
|
||||
"\\(.+\\)",
|
||||
String.format("(value=%s, %s=%s)", newValue, timeUnitField, newTimeUnit.name()));
|
||||
return SuggestedFix.builder()
|
||||
.replace(annotation, synthesizedAnnotation)
|
||||
.addStaticImport(TimeUnit.class.getName() + '.' + newTimeUnit.name())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Optional<Fix> getExplicitAttributesFix(
|
||||
AnnotationTree annotation,
|
||||
Map<String, TimeSimplifier.Simplification> simplifications,
|
||||
String timeUnitField,
|
||||
TimeUnit newUnit,
|
||||
VisitorState state) {
|
||||
return simplifications.entrySet().stream()
|
||||
.map(
|
||||
simplificationEntry ->
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
annotation, state, timeUnitField, ImmutableList.of(newUnit.name()))
|
||||
.merge(
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
annotation,
|
||||
state,
|
||||
simplificationEntry.getKey(),
|
||||
ImmutableList.of(
|
||||
String.valueOf(simplificationEntry.getValue().toUnit(newUnit))))))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(builder -> builder.addStaticImport(TimeUnit.class.getName() + '.' + newUnit.name()))
|
||||
.map(SuggestedFix.Builder::build);
|
||||
}
|
||||
|
||||
private static String getAnnotationFqcn(AnnotationTree annotation) {
|
||||
return ASTHelpers.getSymbol(annotation).getQualifiedName().toString();
|
||||
}
|
||||
|
||||
private static Optional<Number> getValue(
|
||||
String field, ImmutableMap<String, ExpressionTree> indexedArguments) {
|
||||
return Optional.ofNullable(indexedArguments.get(field))
|
||||
.filter(AssignmentTree.class::isInstance)
|
||||
.map(AssignmentTree.class::cast)
|
||||
.map(AssignmentTree::getExpression)
|
||||
.map(expr -> ASTHelpers.constValue(expr, Number.class));
|
||||
}
|
||||
|
||||
private static TimeUnit getTimeUnit(
|
||||
AnnotationTree annotation,
|
||||
String field,
|
||||
ImmutableMap<String, ExpressionTree> indexedArguments) {
|
||||
VarSymbol symbol =
|
||||
Optional.ofNullable(indexedArguments.get(field))
|
||||
.map(
|
||||
argumentTree ->
|
||||
(VarSymbol)
|
||||
ASTHelpers.getSymbol(((AssignmentTree) argumentTree).getExpression()))
|
||||
.orElseGet(() -> getDefaultTimeUnit(annotation, field));
|
||||
return TimeUnit.valueOf(symbol.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
private static VarSymbol getDefaultTimeUnit(AnnotationTree annotation, String argument) {
|
||||
Scope scope = ASTHelpers.getSymbol(annotation).members();
|
||||
MethodSymbol argumentSymbol =
|
||||
(MethodSymbol)
|
||||
Iterables.getOnlyElement(
|
||||
ASTHelpers.scope(scope)
|
||||
.getSymbols(symbol -> symbol.getQualifiedName().contentEquals(argument)));
|
||||
return (VarSymbol)
|
||||
requireNonNull(argumentSymbol.getDefaultValue(), "Default value missing").getValue();
|
||||
}
|
||||
|
||||
private static AnnotationAttributeMatcher createAnnotationAttributeMatcher() {
|
||||
ImmutableList<String> toMatch =
|
||||
Arrays.stream(AnnotationDescriptor.values())
|
||||
.flatMap(
|
||||
annotation ->
|
||||
annotation.timeFields.stream().map(field -> annotation.fqcn + '#' + field))
|
||||
.collect(toImmutableList());
|
||||
return AnnotationAttributeMatcher.create(Optional.of(toMatch), ImmutableList.of());
|
||||
}
|
||||
|
||||
private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
|
||||
checkArgument(
|
||||
value instanceof Integer || value instanceof Long,
|
||||
"Only time expressed as an integer or long can be simplified");
|
||||
return TimeSimplifier.simplify(value.longValue(), unit);
|
||||
}
|
||||
|
||||
private static TimeUnit findCommonUnit(ImmutableSet<TimeUnit> units) {
|
||||
return ImmutableSortedSet.copyOf(units).first();
|
||||
}
|
||||
|
||||
private enum AnnotationDescriptor {
|
||||
JUNIT_TIMEOUT("org.junit.jupiter.api.Timeout", ImmutableSet.of("value"), "unit"),
|
||||
SPRING_SCHEDULED(
|
||||
"org.springframework.scheduling.annotation.Scheduled",
|
||||
ImmutableSet.of("fixedDelay", "fixedRate", "initialDelay"),
|
||||
"timeUnit",
|
||||
ImmutableSet.of("fixedDelayString", "fixedRateString", "initialDelayString"));
|
||||
|
||||
/** The fully-qualified class name of the annotation to simplify. */
|
||||
private final String fqcn;
|
||||
/** The attributes containing a value of time. */
|
||||
private final ImmutableSet<String> timeFields;
|
||||
/** The attribute containing the time unit. */
|
||||
private final String timeUnitField;
|
||||
/** The set of attributes that cause the check to back off. */
|
||||
private final ImmutableSet<String> bannedFields;
|
||||
|
||||
AnnotationDescriptor(String fqcn, ImmutableSet<String> timeFields, String timeUnitField) {
|
||||
this(fqcn, timeFields, timeUnitField, ImmutableSet.of());
|
||||
}
|
||||
|
||||
AnnotationDescriptor(
|
||||
String fqcn,
|
||||
ImmutableSet<String> timeFields,
|
||||
String timeUnitField,
|
||||
ImmutableSet<String> bannedFields) {
|
||||
this.fqcn = fqcn;
|
||||
this.timeFields = timeFields;
|
||||
this.timeUnitField = timeUnitField;
|
||||
this.bannedFields = bannedFields;
|
||||
}
|
||||
|
||||
public static AnnotationDescriptor from(String fqcn) {
|
||||
return Arrays.stream(values())
|
||||
.filter(annotation -> annotation.fqcn.equals(fqcn))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Unknown enum constant: %s.%s",
|
||||
AnnotationDescriptor.class.getName(), fqcn)));
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility class to help simplify time expressions. */
|
||||
private static final class TimeSimplifier {
|
||||
private static final ImmutableSortedSet<TimeUnit> TIME_UNITS =
|
||||
ImmutableSortedSet.copyOf(TimeUnit.values());
|
||||
|
||||
/**
|
||||
* Returns a {@link Simplification} (iff possible) that describes how the {@code originalValue}
|
||||
* and {@code originalUnit} can be simplified using a larger {@link TimeUnit}.
|
||||
*/
|
||||
static Optional<Simplification> simplify(long originalValue, TimeUnit originalUnit) {
|
||||
return descendingLargerUnits(originalUnit).stream()
|
||||
.flatMap(unit -> trySimplify(originalValue, originalUnit, unit))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Stream<Simplification> trySimplify(
|
||||
long originalValue, TimeUnit originalUnit, TimeUnit unit) {
|
||||
long converted = unit.convert(originalValue, originalUnit);
|
||||
// Check whether we lose any precision by checking whether we can convert back.
|
||||
return originalValue == originalUnit.convert(converted, unit)
|
||||
? Stream.of(new Simplification(converted, unit))
|
||||
: Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all time units that represent a larger amount of time than {@code unit}, in
|
||||
* descending order.
|
||||
*/
|
||||
private static ImmutableSortedSet<TimeUnit> descendingLargerUnits(TimeUnit unit) {
|
||||
return TIME_UNITS.tailSet(unit, /* inclusive= */ false).descendingSet();
|
||||
}
|
||||
|
||||
/** Represents a simplification in terms of the new value and new unit. */
|
||||
private static final class Simplification {
|
||||
private final long value;
|
||||
private final TimeUnit timeUnit;
|
||||
|
||||
Simplification(long value, TimeUnit timeUnit) {
|
||||
this.value = value;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the value with the unit represented by this simplification to an equivalent value
|
||||
* in the given {@code unit}.
|
||||
*/
|
||||
public long toUnit(TimeUnit unit) {
|
||||
return unit.convert(value, timeUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
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.isSubtypeOf;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -22,22 +23,23 @@ 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.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} which flags SLF4J usages that are likely to be in error. */
|
||||
/** 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
|
||||
// https://www.slf4j.org/faq.html#paramException. That should be documented.
|
||||
// XXX: Also simplify `LOG.error(String.format("Something %s", arg), throwable)`.
|
||||
// XXX: Also simplify `LOG.error(String.join("sep", arg1, arg2), throwable)`? Perhaps too obscure.
|
||||
// XXX: Write a similar checker for Spring RestTemplates, String.format and friends, Guava
|
||||
// preconditions, ...
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "Slf4jLogStatement",
|
||||
summary = "Make sure SLF4J log statements contain proper placeholders with matching arguments",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "Slf4jLogStatement",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class Slf4jLogStatementCheck extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
public final class Slf4jLogStatement extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
|
||||
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
|
||||
@@ -46,6 +48,9 @@ public final class Slf4jLogStatementCheck extends BugChecker
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
.namedAnyOf("trace", "debug", "info", "warn", "error");
|
||||
|
||||
/** Instantiates a new {@link Slf4jLogStatement} instance. */
|
||||
public Slf4jLogStatement() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!SLF4J_LOGGER_INVOCATION.matches(tree, state)) {
|
||||
@@ -114,7 +119,7 @@ public final class Slf4jLogStatementCheck extends BugChecker
|
||||
* replaced at this usage site.
|
||||
*/
|
||||
description.addFix(
|
||||
SuggestedFix.replace(tree, Util.treeToString(tree, state).replace("%s", "{}")));
|
||||
SuggestedFix.replace(tree, SourceCode.treeToString(tree, state).replace("%s", "{}")));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1,11 +1,12 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static java.util.function.Predicate.not;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
@@ -25,20 +26,22 @@ 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.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@code @RequestMapping} annotations that can be written more
|
||||
* A {@link BugChecker} that flags {@code @RequestMapping} annotations that can be written more
|
||||
* concisely.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "SpringMvcAnnotation",
|
||||
summary =
|
||||
"Prefer the conciseness of `@{Get,Put,Post,Delete,Patch}Mapping` over `@RequestMapping`",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "SpringMvcAnnotation",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class SpringMvcAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
|
||||
public final class SpringMvcAnnotation extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
|
||||
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
|
||||
@@ -54,6 +57,9 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
|
||||
.put("PUT", "PutMapping")
|
||||
.build();
|
||||
|
||||
/** Instantiates a new {@link SpringMvcAnnotation} instance. */
|
||||
public SpringMvcAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
// XXX: We could remove the `@RequestMapping` import if not other usages remain.
|
||||
@@ -92,7 +98,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
|
||||
private static String extractMethod(ExpressionTree expr, VisitorState state) {
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
return Util.treeToString(expr, state);
|
||||
return SourceCode.treeToString(expr, state);
|
||||
case MEMBER_SELECT:
|
||||
return ((MemberSelectTree) expr).getIdentifier().toString();
|
||||
default:
|
||||
@@ -105,7 +111,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
|
||||
String newArguments =
|
||||
tree.getArguments().stream()
|
||||
.filter(not(argToRemove::equals))
|
||||
.map(arg -> Util.treeToString(arg, state))
|
||||
.map(arg -> SourceCode.treeToString(arg, state))
|
||||
.collect(joining(", "));
|
||||
|
||||
return SuggestedFix.builder()
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
@@ -27,15 +28,14 @@ import com.sun.tools.javac.code.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags methods and constants that can and should be statically
|
||||
* imported.
|
||||
* 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"}`
|
||||
// XXX: Also introduce a check which disallows static imports of certain methods. Candidates:
|
||||
// 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`
|
||||
@@ -45,12 +45,12 @@ import java.util.Optional;
|
||||
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "StaticImport",
|
||||
summary = "Identifier should be statically imported",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "StaticImport",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class StaticImportCheck extends BugChecker implements MemberSelectTreeMatcher {
|
||||
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
@@ -67,6 +67,7 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
|
||||
"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",
|
||||
@@ -100,7 +101,8 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
|
||||
"org.springframework.http.HttpMethod",
|
||||
"org.springframework.http.MediaType",
|
||||
"org.testng.Assert",
|
||||
"reactor.function.TupleUtils");
|
||||
"reactor.function.TupleUtils",
|
||||
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
|
||||
|
||||
/** Type members that should be statically imported. */
|
||||
@VisibleForTesting
|
||||
@@ -189,6 +191,9 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
|
||||
"of",
|
||||
"valueOf");
|
||||
|
||||
/** Instantiates a new {@link StaticImport} instance. */
|
||||
public StaticImport() {}
|
||||
|
||||
@Override
|
||||
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
|
||||
if (!isCandidateContext(state) || !isCandidate(tree)) {
|
||||
@@ -0,0 +1,187 @@
|
||||
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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
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.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
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.Convert;
|
||||
import java.util.Formattable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link String#format(String, Object...)} invocations which can be
|
||||
* replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
|
||||
* String#valueOf} invocation.
|
||||
*/
|
||||
// XXX: What about `v1 + "sep" + v2` and similar expressions? Do we want to rewrite those to
|
||||
// `String.join`, or should some `String.join` invocations be rewritten to use the `+` operator?
|
||||
// (The latter suggestion would conflict with the `FormatStringConcatenation` check.)
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `String#join` over `String#format`",
|
||||
link = BUG_PATTERNS_BASE_URL + "StringJoin",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class StringJoin extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Splitter FORMAT_SPECIFIER_SPLITTER = Splitter.on("%s");
|
||||
private static final Matcher<ExpressionTree> STRING_FORMAT_INVOCATION =
|
||||
staticMethod().onClass(String.class.getName()).named("format");
|
||||
private static final Supplier<Type> CHAR_SEQUENCE_TYPE =
|
||||
Suppliers.typeFromClass(CharSequence.class);
|
||||
private static final Supplier<Type> FORMATTABLE_TYPE = Suppliers.typeFromClass(Formattable.class);
|
||||
|
||||
/** Instantiates a new {@link StringJoin} instance. */
|
||||
public StringJoin() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!STRING_FORMAT_INVOCATION.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
// XXX: This check assumes that if the first argument to `String#format` is a `Locale`, that
|
||||
// this argument is not vacuous, and that as a result the expression cannot be simplified using
|
||||
// `#valueOf` or `#join`. Implement a separate check that identifies and drops redundant
|
||||
// `Locale` arguments. See also a related comment in `FormatStringConcatenation`.
|
||||
String formatString = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
|
||||
if (formatString == null) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
List<String> separators = FORMAT_SPECIFIER_SPLITTER.splitToList(formatString);
|
||||
if (separators.size() < 2) {
|
||||
/* The format string does not contain `%s` format specifiers. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
if (separators.size() != tree.getArguments().size()) {
|
||||
/* The number of arguments does not match the number of `%s` format specifiers. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
int lastIndex = separators.size() - 1;
|
||||
if (!separators.get(0).isEmpty() || !separators.get(lastIndex).isEmpty()) {
|
||||
/* The format string contains leading or trailing characters. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableSet<String> innerSeparators = ImmutableSet.copyOf(separators.subList(1, lastIndex));
|
||||
if (innerSeparators.size() > 1) {
|
||||
/* The `%s` format specifiers are not uniformly separated. */
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
if (innerSeparators.isEmpty()) {
|
||||
/*
|
||||
* This `String#format` invocation performs a straightforward string conversion; use
|
||||
* `String#valueOf` instead.
|
||||
*/
|
||||
return trySuggestExplicitStringConversion(tree, state);
|
||||
}
|
||||
|
||||
String separator = Iterables.getOnlyElement(innerSeparators);
|
||||
if (separator.indexOf('%') >= 0) {
|
||||
/* The `%s` format specifiers are separated by another format specifier. */
|
||||
// XXX: Strictly speaking we could support `%%` by mapping it to a literal `%`, but that
|
||||
// doesn't seem worth the trouble.
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return trySuggestExplicitJoin(tree, separator, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* If guaranteed to be behavior preserving, suggests replacing {@code String.format("%s", arg)}
|
||||
* with {@code String.valueOf(arg)}.
|
||||
*
|
||||
* <p>If {@code arg} is already a string then the resultant conversion is vacuous. The {@link
|
||||
* IdentityConversion} check will subsequently drop it.
|
||||
*/
|
||||
private Description trySuggestExplicitStringConversion(
|
||||
MethodInvocationTree tree, VisitorState state) {
|
||||
ExpressionTree argument = tree.getArguments().get(1);
|
||||
if (isSubtype(ASTHelpers.getType(argument), FORMATTABLE_TYPE, state)) {
|
||||
/*
|
||||
* `Formattable` arguments are handled specially; `String#valueOf` is not a suitable
|
||||
* alternative.
|
||||
*/
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.setMessage("Prefer `String#valueOf` over `String#format`")
|
||||
.addFix(SuggestedFix.replace(tree, withStringConversionExpression(argument, state)))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unless the given {@code String.format} expression includes {@link Formattable} arguments,
|
||||
* suggests replacing it with a {@code String.join} expression using the specified argument
|
||||
* separator.
|
||||
*/
|
||||
private Description trySuggestExplicitJoin(
|
||||
MethodInvocationTree tree, String separator, VisitorState state) {
|
||||
Iterator<? extends ExpressionTree> arguments = tree.getArguments().iterator();
|
||||
|
||||
SuggestedFix.Builder fix =
|
||||
SuggestedFix.builder()
|
||||
.replace(tree.getMethodSelect(), "String.join")
|
||||
.replace(arguments.next(), String.format("\"%s\"", Convert.quote(separator)));
|
||||
|
||||
while (arguments.hasNext()) {
|
||||
ExpressionTree argument = arguments.next();
|
||||
Type argumentType = ASTHelpers.getType(argument);
|
||||
if (isSubtype(argumentType, FORMATTABLE_TYPE, state)) {
|
||||
/*
|
||||
* `Formattable` arguments are handled specially; `String#join` is not a suitable
|
||||
* alternative.
|
||||
*/
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
if (!isSubtype(argumentType, CHAR_SEQUENCE_TYPE, state)) {
|
||||
/*
|
||||
* The argument was previously implicitly converted to a string; now this must happen
|
||||
* explicitly.
|
||||
*/
|
||||
fix.replace(argument, withStringConversionExpression(argument, state));
|
||||
}
|
||||
}
|
||||
|
||||
return describeMatch(tree, fix.build());
|
||||
}
|
||||
|
||||
private static boolean isSubtype(
|
||||
@Nullable Type subType, Supplier<Type> superType, VisitorState state) {
|
||||
return ASTHelpers.isSubtype(subType, superType.get(state), state);
|
||||
}
|
||||
|
||||
private static String withStringConversionExpression(
|
||||
ExpressionTree argument, VisitorState state) {
|
||||
return String.format("String.valueOf(%s)", SourceCode.treeToString(argument, state));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
@@ -10,6 +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.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -25,17 +26,20 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/** A {@link BugChecker} which flags illegal time-zone related operations. */
|
||||
/** A {@link BugChecker} that flags illegal time-zone related operations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "TimeZoneUsage",
|
||||
summary =
|
||||
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "TimeZoneUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = FRAGILE_CODE)
|
||||
public final class TimeZoneUsageCheck extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
public final class TimeZoneUsage extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> BANNED_TIME_METHOD =
|
||||
anyOf(
|
||||
@@ -57,10 +61,16 @@ public final class TimeZoneUsageCheck extends BugChecker implements MethodInvoca
|
||||
.onClassAny(
|
||||
LocalDate.class.getName(),
|
||||
LocalDateTime.class.getName(),
|
||||
LocalTime.class.getName())
|
||||
LocalTime.class.getName(),
|
||||
OffsetDateTime.class.getName(),
|
||||
OffsetTime.class.getName(),
|
||||
ZonedDateTime.class.getName())
|
||||
.named("now"),
|
||||
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
|
||||
|
||||
/** Instantiates a new {@link TimeZoneUsage} instance. */
|
||||
public TimeZoneUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return BANNED_TIME_METHOD.matches(tree, state)
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Picnic Error Prone Contrib checks. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
@@ -28,7 +28,7 @@ import java.util.stream.Stream;
|
||||
* <p>This class allows one to define a whitelist or blacklist of annotations or their attributes.
|
||||
* Annotations are identified by their fully qualified name.
|
||||
*/
|
||||
final class AnnotationAttributeMatcher implements Serializable {
|
||||
public final class AnnotationAttributeMatcher implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean complement;
|
||||
@@ -59,7 +59,7 @@ final class AnnotationAttributeMatcher implements Serializable {
|
||||
* @param exclusions The listed annotations or annotation attributes are not matched.
|
||||
* @return A non-{@code null} {@link AnnotationAttributeMatcher}.
|
||||
*/
|
||||
static AnnotationAttributeMatcher create(
|
||||
public static AnnotationAttributeMatcher create(
|
||||
Optional<? extends List<String>> inclusions, Iterable<String> exclusions) {
|
||||
Set<String> includedWholeTypes = new HashSet<>();
|
||||
Set<String> excludedWholeTypes = new HashSet<>();
|
||||
@@ -97,7 +97,13 @@ final class AnnotationAttributeMatcher implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
|
||||
/**
|
||||
* Returns the subset of arguments of the given {@link AnnotationTree} matched by this instance.
|
||||
*
|
||||
* @param tree The annotation AST node to be inspected.
|
||||
* @return Any matching annotation arguments.
|
||||
*/
|
||||
public Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
|
||||
Type type = ASTHelpers.getType(tree.getAnnotationType());
|
||||
if (type == null) {
|
||||
return Stream.empty();
|
||||
@@ -0,0 +1,9 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
/** Utility class providing documentation-related code. */
|
||||
public final class Documentation {
|
||||
/** The base URL at which Error Prone Support bug patterns are hosted. */
|
||||
public static final String BUG_PATTERNS_BASE_URL = "https://error-prone.picnic.tech/bugpatterns/";
|
||||
|
||||
private Documentation() {}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
final class JavaKeywords {
|
||||
/** Utility class that can be used to identify reserved keywords of the Java language. */
|
||||
public final class JavaKeywords {
|
||||
/**
|
||||
* List of all reserved keywords in the Java language.
|
||||
*
|
||||
@@ -95,17 +96,33 @@ final class JavaKeywords {
|
||||
|
||||
private JavaKeywords() {}
|
||||
|
||||
/** Tells whether the given string is a reserved keyword in the Java language. */
|
||||
/**
|
||||
* Tells whether the given string is a reserved keyword in the Java language.
|
||||
*
|
||||
* @param str The string of interest.
|
||||
* @return {@code true} if the given string is a reserved keyword in the Java language.
|
||||
*/
|
||||
public static boolean isReservedKeyword(String str) {
|
||||
return RESERVED_KEYWORDS.contains(str);
|
||||
}
|
||||
|
||||
/** Tells whether the given string is a contextual keyword in the Java language. */
|
||||
/**
|
||||
* Tells whether the given string is a contextual keyword in the Java language.
|
||||
*
|
||||
* @param str The string of interest.
|
||||
* @return {@code true} if the given string is a contextual keyword in the Java language.
|
||||
*/
|
||||
public static boolean isContextualKeyword(String str) {
|
||||
return CONTEXTUAL_KEYWORDS.contains(str);
|
||||
}
|
||||
|
||||
/** Tells whether the given string is a reserved or contextual keyword in the Java language. */
|
||||
/**
|
||||
* Tells whether the given string is a reserved or contextual keyword in the Java language.
|
||||
*
|
||||
* @param str The string of interest.
|
||||
* @return {@code true} if the given string is a reserved or contextual keyword in the Java
|
||||
* language.
|
||||
*/
|
||||
public static boolean isKeyword(String str) {
|
||||
return ALL_KEYWORDS.contains(str);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
@@ -15,13 +15,23 @@ import java.util.regex.Pattern;
|
||||
/** A method invocation expression {@link Matcher} factory. */
|
||||
// XXX: Document better. The expressions accepted here could also be defined using `MethodMatchers`.
|
||||
// So explain why this class is still useful.
|
||||
final class MethodMatcherFactory {
|
||||
public final class MethodMatcherFactory {
|
||||
private static final Splitter ARGUMENT_TYPE_SPLITTER =
|
||||
Splitter.on(',').trimResults().omitEmptyStrings();
|
||||
private static final Pattern METHOD_SIGNATURE =
|
||||
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
|
||||
|
||||
Matcher<ExpressionTree> create(Collection<String> signatures) {
|
||||
/** Instantiates a new {@link MethodMatcherFactory} instance. */
|
||||
public MethodMatcherFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a {@link Matcher} of methods with any of the given signatures.
|
||||
*
|
||||
* @param signatures The method signatures of interest.
|
||||
* @return A new {@link Matcher} which accepts invocation expressions of any method identified by
|
||||
* the given signatures.
|
||||
*/
|
||||
public Matcher<ExpressionTree> create(Collection<String> signatures) {
|
||||
return anyOf(
|
||||
signatures.stream()
|
||||
.map(MethodMatcherFactory::createMethodMatcher)
|
||||
@@ -0,0 +1,132 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.tools.javac.code.BoundKind;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A set of helper methods which together define a DSL for defining {@link Type types}.
|
||||
*
|
||||
* <p>These methods are meant to be statically imported. Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Supplier<Type> type =
|
||||
* VisitorState.memoize(
|
||||
* generic(
|
||||
* type("reactor.core.publisher.Flux"),
|
||||
* subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
* }</pre>
|
||||
*
|
||||
* This statement produces a memoized supplier of the type {@code Flux<? extends Publisher<?>>}.
|
||||
*/
|
||||
public final class MoreTypes {
|
||||
private MoreTypes() {}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the type with the given fully qualified name.
|
||||
*
|
||||
* <p>This method should only be used when building more complex types in combination with other
|
||||
* {@link MoreTypes} methods. In other cases prefer directly calling {@link
|
||||
* Suppliers#typeFromString(String)}.
|
||||
*
|
||||
* @param typeName The type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> type(String typeName) {
|
||||
return Suppliers.typeFromString(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the described generic type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @param typeArgs The desired type arguments.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
// XXX: The given `type` should be a generic type, so perhaps `withParams` would be a better
|
||||
// method name. But the DSL wouldn't look as nice that way.
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) -> {
|
||||
List<Type> params =
|
||||
Arrays.stream(typeArgs).map(s -> s.get(state)).collect(toCollection(ArrayList::new));
|
||||
if (params.stream().anyMatch(Objects::isNull)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.getType(baseType, /* isArray= */ false, params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a raw (erased, non-generic) variant of the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> raw(Supplier<Type> type) {
|
||||
return propagateNull(type, (state, baseType) -> baseType.tsym.erasure(state.getTypes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? super T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> superOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(baseType, BoundKind.SUPER, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? extends T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> subOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(
|
||||
type.get(state), BoundKind.EXTENDS, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unbound wildcard type ({@code ?}).
|
||||
*
|
||||
* @return A supplier which returns the described type.
|
||||
*/
|
||||
public static Supplier<Type> unbound() {
|
||||
return state ->
|
||||
new Type.WildcardType(
|
||||
state.getSymtab().objectType, BoundKind.UNBOUND, state.getSymtab().boundClass);
|
||||
}
|
||||
|
||||
private static Supplier<Type> propagateNull(
|
||||
Supplier<Type> type, BiFunction<VisitorState, Type, Type> transformer) {
|
||||
return state ->
|
||||
Optional.ofNullable(type.get(state)).map(t -> transformer.apply(state, t)).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.sun.source.tree.Tree;
|
||||
|
||||
/**
|
||||
* A collection of Error Prone utility methods for dealing with the source code representation of
|
||||
* AST nodes.
|
||||
*/
|
||||
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
|
||||
final class Util {
|
||||
private Util() {}
|
||||
public final class SourceCode {
|
||||
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.
|
||||
*/
|
||||
static String treeToString(Tree tree, VisitorState state) {
|
||||
public static String treeToString(Tree tree, VisitorState state) {
|
||||
String src = state.getSourceForNode(tree);
|
||||
return src != null ? src : tree.toString();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.sun.tools.javac.code.ClassFinder;
|
||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
|
||||
/**
|
||||
* Utility class that helps decide whether it is appropriate to introduce references to (well-known)
|
||||
* third-party libraries.
|
||||
*
|
||||
* <p>This class should be used by {@link BugChecker}s that may otherwise suggest the introduction
|
||||
* of code that depends on possibly-not-present third-party libraries.
|
||||
*/
|
||||
// XXX: Consider giving users more fine-grained control. This would be beneficial in cases where a
|
||||
// dependency is on the classpath, but new usages are undesirable.
|
||||
public enum ThirdPartyLibrary {
|
||||
/**
|
||||
* AssertJ.
|
||||
*
|
||||
* @see <a href="https://assertj.github.io/doc">AssertJ documentation</a>
|
||||
*/
|
||||
ASSERTJ("org.assertj.core.api.Assertions"),
|
||||
/**
|
||||
* Google's Guava.
|
||||
*
|
||||
* @see <a href="https://github.com/google/guava">Guava on GitHub</a>
|
||||
*/
|
||||
GUAVA("com.google.common.collect.ImmutableList"),
|
||||
/**
|
||||
* New Relic's Java agent API.
|
||||
*
|
||||
* @see <a href="https://github.com/newrelic/newrelic-java-agent/tree/main/newrelic-api">New Relic
|
||||
* Java agent API on GitHub</a>
|
||||
*/
|
||||
NEW_RELIC_AGENT_API("com.newrelic.api.agent.Agent"),
|
||||
/**
|
||||
* VMWare's Project Reactor.
|
||||
*
|
||||
* @see <a href="https://projectreactor.io">Home page</a>
|
||||
*/
|
||||
REACTOR("reactor.core.publisher.Flux");
|
||||
|
||||
private static final String IGNORE_CLASSPATH_COMPAT_FLAG =
|
||||
"ErrorProneSupport:IgnoreClasspathCompat";
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker" /* Supplier is deterministic. */)
|
||||
private final Supplier<Boolean> canUse;
|
||||
|
||||
/**
|
||||
* Instantiates a {@link ThirdPartyLibrary} enum value.
|
||||
*
|
||||
* @param witnessFqcn The fully-qualified class name of a type that is expected to be on the
|
||||
* classpath iff the associated third-party library is on the classpath.
|
||||
*/
|
||||
ThirdPartyLibrary(String witnessFqcn) {
|
||||
this.canUse = VisitorState.memoize(state -> canIntroduceUsage(witnessFqcn, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether it is okay to introduce a dependency on this well-known third party library in
|
||||
* the given context.
|
||||
*
|
||||
* @param state The context under consideration.
|
||||
* @return {@code true} iff it is okay to assume or create a dependency on this library.
|
||||
*/
|
||||
public boolean isIntroductionAllowed(VisitorState state) {
|
||||
return canUse.get(state);
|
||||
}
|
||||
|
||||
private static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to determine whether a class with the given FQCN is on the classpath.
|
||||
*
|
||||
* <p>The {@link VisitorState}'s symbol table is consulted first. If the type has not yet been
|
||||
* loaded, then an attempt is made to do so.
|
||||
*/
|
||||
private static boolean isKnownClass(String className, VisitorState state) {
|
||||
return state.getTypeFromString(className) != null || canLoadClass(className, state);
|
||||
}
|
||||
|
||||
private static boolean canLoadClass(String className, VisitorState state) {
|
||||
ClassFinder classFinder = ClassFinder.instance(state.context);
|
||||
Name binaryName = state.binaryNameFromClassname(className);
|
||||
try {
|
||||
classFinder.loadClass(state.getSymtab().unnamedModule, binaryName);
|
||||
return true;
|
||||
} catch (CompletionFailure e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreClasspath(VisitorState state) {
|
||||
return state
|
||||
.errorProneOptions()
|
||||
.getFlags()
|
||||
.getBoolean(IGNORE_CLASSPATH_COMPAT_FLAG)
|
||||
.orElse(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Auxiliary utilities for use by Error Prone checks. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -9,19 +9,21 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.math.BigDecimal;
|
||||
import org.assertj.core.api.AbstractBigDecimalAssert;
|
||||
import org.assertj.core.api.BigDecimalAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
* Refaster templates related to AssertJ assertions over {@link BigDecimal}s.
|
||||
* Refaster rules related to AssertJ assertions over {@link BigDecimal}s.
|
||||
*
|
||||
* <p>Note that, contrary to collections of Refaster templates for other {@link
|
||||
* org.assertj.core.api.NumberAssert} subtypes, these templates do not rewrite to/from {@link
|
||||
* <p>Note that, contrary to collections of Refaster rules for other {@link
|
||||
* org.assertj.core.api.NumberAssert} subtypes, these rules do not rewrite to/from {@link
|
||||
* BigDecimalAssert#isEqualTo(Object)} and {@link BigDecimalAssert#isNotEqualTo(Object)}. This is
|
||||
* because {@link BigDecimal#equals(Object)} considers not only the numeric value of compared
|
||||
* instances, but also their scale. As a result various seemingly straightforward transformations
|
||||
* would actually subtly change the assertion's semantics.
|
||||
*/
|
||||
final class AssertJBigDecimalTemplates {
|
||||
private AssertJBigDecimalTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJBigDecimalRules {
|
||||
private AssertJBigDecimalRules() {}
|
||||
|
||||
static final class AbstractBigDecimalAssertIsEqualByComparingTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -8,11 +8,13 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.math.BigInteger;
|
||||
import org.assertj.core.api.AbstractBigIntegerAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
// XXX: If we add a rule which drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
|
||||
// XXX: If we add a rule that drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
|
||||
// cases below can go.
|
||||
final class AssertJBigIntegerTemplates {
|
||||
private AssertJBigIntegerTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJBigIntegerRules {
|
||||
private AssertJBigIntegerRules() {}
|
||||
|
||||
static final class AbstractBigIntegerAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -8,9 +8,11 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJBooleanTemplates {
|
||||
private AssertJBooleanTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJBooleanRules {
|
||||
private AssertJBooleanRules() {}
|
||||
|
||||
static final class AbstractBooleanAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -7,9 +7,11 @@ import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractByteAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJByteTemplates {
|
||||
private AssertJByteTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJByteRules {
|
||||
private AssertJByteRules() {}
|
||||
|
||||
static final class AbstractByteAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -8,9 +8,11 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJCharSequenceTemplates {
|
||||
private AssertJCharSequenceTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJCharSequenceRules {
|
||||
private AssertJCharSequenceRules() {}
|
||||
|
||||
static final class AssertThatCharSequenceIsEmpty {
|
||||
@BeforeTemplate
|
||||
@@ -0,0 +1,94 @@
|
||||
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.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractComparableAssert;
|
||||
import org.assertj.core.api.AbstractIntegerAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@OnlineDocumentation
|
||||
final class AssertJComparableRules {
|
||||
private AssertJComparableRules() {}
|
||||
|
||||
static final class AssertThatIsEqualByComparingTo<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isEqualByComparingTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsNotEqualByComparingTo<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isNotEqualTo(0);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isNotEqualByComparingTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsLessThan<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isNegative();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isLessThan(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsLessThanOrEqualTo<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isNotPositive();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isLessThanOrEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsGreaterThan<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isPositive();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isGreaterThan(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsGreaterThanOrEqualTo<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(T actual, T expected) {
|
||||
return assertThat(actual.compareTo(expected)).isNotNegative();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractComparableAssert<?, ?> after(T actual, T expected) {
|
||||
return assertThat(actual).isGreaterThanOrEqualTo(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -8,9 +8,11 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractDoubleAssert;
|
||||
import org.assertj.core.data.Offset;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJDoubleTemplates {
|
||||
private AssertJDoubleTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJDoubleRules {
|
||||
private AssertJDoubleRules() {}
|
||||
|
||||
static final class AbstractDoubleAssertIsCloseToWithOffset {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
@@ -6,9 +6,11 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Collection;
|
||||
import org.assertj.core.api.EnumerableAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJEnumerableTemplates {
|
||||
private AssertJEnumerableTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJEnumerableRules {
|
||||
private AssertJEnumerableRules() {}
|
||||
|
||||
static final class EnumerableAssertIsEmpty<E> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -8,9 +8,11 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractFloatAssert;
|
||||
import org.assertj.core.data.Offset;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJFloatTemplates {
|
||||
private AssertJFloatTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJFloatRules {
|
||||
private AssertJFloatRules() {}
|
||||
|
||||
static final class AbstractFloatAssertIsCloseToWithOffset {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -7,9 +7,11 @@ import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractIntegerAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJIntegerTemplates {
|
||||
private AssertJIntegerTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJIntegerRules {
|
||||
private AssertJIntegerRules() {}
|
||||
|
||||
static final class AbstractIntegerAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -7,9 +7,11 @@ import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractLongAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJLongTemplates {
|
||||
private AssertJLongTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJLongRules {
|
||||
private AssertJLongRules() {}
|
||||
|
||||
static final class AbstractLongAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,13 +1,15 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Map;
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJMapTemplates {
|
||||
private AssertJMapTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJMapRules {
|
||||
private AssertJMapRules() {}
|
||||
|
||||
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
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.NotMatches;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
@@ -18,9 +19,12 @@ import org.assertj.core.api.AbstractIntegerAssert;
|
||||
import org.assertj.core.api.AbstractLongAssert;
|
||||
import org.assertj.core.api.AbstractShortAssert;
|
||||
import org.assertj.core.api.NumberAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsCharacter;
|
||||
|
||||
final class AssertJNumberTemplates {
|
||||
private AssertJNumberTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJNumberRules {
|
||||
private AssertJNumberRules() {}
|
||||
|
||||
static final class NumberAssertIsPositive {
|
||||
@BeforeTemplate
|
||||
@@ -226,9 +230,16 @@ final class AssertJNumberTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link AbstractLongAssert#isOdd()} (and similar methods for other {@link NumberAssert}
|
||||
* subtypes) over alternatives with less informative error messages.
|
||||
*
|
||||
* <p>Note that {@link org.assertj.core.api.AbstractCharacterAssert} does not implement {@link
|
||||
* NumberAssert} and does not provide an {@code isOdd} test.
|
||||
*/
|
||||
static final class AssertThatIsOdd {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(int number) {
|
||||
AbstractIntegerAssert<?> before(@NotMatches(IsCharacter.class) int number) {
|
||||
return assertThat(number % 2).isEqualTo(1);
|
||||
}
|
||||
|
||||
@@ -244,9 +255,16 @@ final class AssertJNumberTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link AbstractLongAssert#isEven()} (and similar methods for other {@link NumberAssert}
|
||||
* subtypes) over alternatives with less informative error messages.
|
||||
*
|
||||
* <p>Note that {@link org.assertj.core.api.AbstractCharacterAssert} does not implement {@link
|
||||
* NumberAssert} and does not provide an {@code isEven} test.
|
||||
*/
|
||||
static final class AssertThatIsEven {
|
||||
@BeforeTemplate
|
||||
AbstractIntegerAssert<?> before(int number) {
|
||||
AbstractIntegerAssert<?> before(@NotMatches(IsCharacter.class) int number) {
|
||||
return assertThat(number % 2).isEqualTo(0);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -10,9 +10,11 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJObjectTemplates {
|
||||
private AssertJObjectTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJObjectRules {
|
||||
private AssertJObjectRules() {}
|
||||
|
||||
static final class AssertThatIsInstanceOf<S, T> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -14,9 +14,11 @@ import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.AbstractOptionalAssert;
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.assertj.core.api.OptionalAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJOptionalTemplates {
|
||||
private AssertJOptionalTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJOptionalRules {
|
||||
private AssertJOptionalRules() {}
|
||||
|
||||
static final class AssertThatOptional<T> {
|
||||
@BeforeTemplate
|
||||
@@ -0,0 +1,113 @@
|
||||
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.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import org.assertj.core.api.AbstractDoubleAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@OnlineDocumentation
|
||||
final class AssertJPrimitiveRules {
|
||||
private AssertJPrimitiveRules() {}
|
||||
|
||||
static final class AssertThatIsEqualTo {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(boolean actual, boolean expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual == expected).isTrue(), assertThat(actual != expected).isFalse());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual == expected).isTrue(), assertThat(actual != expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractBooleanAssert<?> after(boolean actual, boolean expected) {
|
||||
return assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsNotEqualTo {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(boolean actual, boolean expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual != expected).isTrue(), assertThat(actual == expected).isFalse());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual != expected).isTrue(), assertThat(actual == expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractBooleanAssert<?> after(boolean actual, boolean expected) {
|
||||
return assertThat(actual).isNotEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsLessThan {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual < expected).isTrue(), assertThat(actual >= expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractDoubleAssert<?> after(double actual, double expected) {
|
||||
return assertThat(actual).isLessThan(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsLessThanOrEqualTo {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual <= expected).isTrue(), assertThat(actual > expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractDoubleAssert<?> after(double actual, double expected) {
|
||||
return assertThat(actual).isLessThanOrEqualTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsGreaterThan {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual > expected).isTrue(), assertThat(actual <= expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractDoubleAssert<?> after(double actual, double expected) {
|
||||
return assertThat(actual).isGreaterThan(expected);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatIsGreaterThanOrEqualTo {
|
||||
@BeforeTemplate
|
||||
AbstractBooleanAssert<?> before(double actual, double expected) {
|
||||
return Refaster.anyOf(
|
||||
assertThat(actual >= expected).isTrue(), assertThat(actual < expected).isFalse());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractDoubleAssert<?> after(double actual, double expected) {
|
||||
return assertThat(actual).isGreaterThanOrEqualTo(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -53,9 +53,10 @@ import org.assertj.core.api.ObjectEnumerableAssert;
|
||||
import org.assertj.core.api.OptionalDoubleAssert;
|
||||
import org.assertj.core.api.OptionalIntAssert;
|
||||
import org.assertj.core.api.OptionalLongAssert;
|
||||
import tech.picnic.errorprone.refaster.util.IsArray;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsArray;
|
||||
|
||||
/** Refaster templates related to AssertJ expressions and statements. */
|
||||
/** Refaster rules related to AssertJ expressions and statements. */
|
||||
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
|
||||
// these in separate files.
|
||||
// XXX: Also do for BigInteger/BigDecimal?
|
||||
@@ -63,7 +64,7 @@ import tech.picnic.errorprone.refaster.util.IsArray;
|
||||
// ^ And variants.
|
||||
// XXX: Consider splitting this class into multiple classes.
|
||||
// XXX: Some of these rules may not apply given the updated TestNG rewrite rules. Review.
|
||||
// XXX: For the templates which "unwrap" explicitly enumerated collections, also introduce variants
|
||||
// XXX: For the rules that "unwrap" explicitly enumerated collections, also introduce variants
|
||||
// with explicitly enumerated sorted collections. (Requires that the type bound is Comparable.)
|
||||
// XXX: Handle `.isEqualTo(explicitlyEnumeratedCollection)`. Can be considered equivalent to
|
||||
// `.containsOnly(elements)`. (This does mean the auto-generated code needs to be more advanced.
|
||||
@@ -101,27 +102,28 @@ import tech.picnic.errorprone.refaster.util.IsArray;
|
||||
// (etc.)
|
||||
// XXX: Look into using Assertions#contentOf(URL url, Charset charset) instead of our own test
|
||||
// method.
|
||||
// XXX: Write Optional templates also for `OptionalInt` and variants.
|
||||
// XXX: Write `Optional` rules also for `OptionalInt` and variants.
|
||||
// XXX: Write plugin to flag `assertThat(compileTimeConstant)` occurrences. Also other likely
|
||||
// candidates, such as `assertThat(ImmutableSet(foo, bar)).XXX`
|
||||
// XXX: Write generic plugin to replace explicit array parameters with varargs (`new int[] {1, 2}`
|
||||
// -> `1, 2`).
|
||||
// XXX: Write plugin which drops any `.withFailMessage` which doesn't include a compile-time
|
||||
// constant string? Most of these are useless.
|
||||
// XXX: Write plugin which identifies `.get().propertyAccess()` and "pushes" this out. Would only
|
||||
// XXX: Write plugin that drops any `.withFailMessage` that doesn't include a compile-time constant
|
||||
// string? Most of these are useless.
|
||||
// XXX: Write plugin that identifies `.get().propertyAccess()` and "pushes" this out. Would only
|
||||
// nicely work for non-special types, though, cause after `extracting(propertyAccess)` many
|
||||
// operations are not available...
|
||||
// XXX: Write plugin which identifies repeated `assertThat(someProp.xxx)` calls and bundles these
|
||||
// XXX: Write plugin that identifies repeated `assertThat(someProp.xxx)` calls and bundles these
|
||||
// somehow.
|
||||
// XXX: `abstractOptionalAssert.get().satisfies(pred)` ->
|
||||
// `abstractOptionalAssert.hasValueSatisfying(pred)`.
|
||||
// XXX: `assertThat(ImmutableList.sortedCopyOf(cmp, values)).somethingExactOrder` -> just compare
|
||||
// "in any order".
|
||||
// XXX: Turns out a lot of this is also covered by https://github.com/palantir/assertj-automation.
|
||||
// See how we can combine these things. Do note that (at present) their Refaster templates don't
|
||||
// See how we can combine these things. Do note that (at present) their Refaster rules don't
|
||||
// show up as Error Prone checks. So we'd have to build an integration for that.
|
||||
final class AssertJTemplates {
|
||||
private AssertJTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJRules {
|
||||
private AssertJRules() {}
|
||||
|
||||
//
|
||||
// OptionalDouble
|
||||
@@ -491,7 +493,7 @@ final class AssertJTemplates {
|
||||
}
|
||||
|
||||
// XXX: This overload is here because `assertThat` has an overload for `Comparable` types.
|
||||
// Unfortunately this still doesn't convince Refaster to match this template in the context of
|
||||
// Unfortunately this still doesn't convince Refaster to match this rule in the context of
|
||||
// Comparable types. Figure out why! Note that this also affects the `AssertThatOptional` rule.
|
||||
static final class AssertThatIterableHasOneComparableElementEqualTo<
|
||||
S extends Comparable<? super S>, T extends S> {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static org.assertj.core.data.Offset.offset;
|
||||
import static org.assertj.core.data.Percentage.withPercentage;
|
||||
@@ -7,9 +7,11 @@ import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import org.assertj.core.api.AbstractShortAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJShortTemplates {
|
||||
private AssertJShortTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJShortRules {
|
||||
private AssertJShortRules() {}
|
||||
|
||||
static final class AbstractShortAssertIsEqualTo {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -8,9 +8,11 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.assertj.core.api.AbstractAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
final class AssertJStringTemplates {
|
||||
private AssertJStringTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJStringRules {
|
||||
private AssertJStringRules() {}
|
||||
|
||||
static final class AbstractStringAssertStringIsEmpty {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -16,18 +16,20 @@ import java.io.IOException;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.AbstractThrowableAssert;
|
||||
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
* Refaster templates related to AssertJ assertions over expressions that may throw a {@link
|
||||
* Throwable} subtype.
|
||||
* Refaster rules related to AssertJ assertions over expressions that may throw a {@link Throwable}
|
||||
* subtype.
|
||||
*
|
||||
* <p>For reasons of consistency we prefer {@link
|
||||
* org.assertj.core.api.Assertions#assertThatThrownBy} over static methods for specific exception
|
||||
* types. Note that only the most common assertion expressions are rewritten here; covering all
|
||||
* cases would require the implementation of an Error Prone check instead.
|
||||
*/
|
||||
final class AssertJThrowingCallableTemplates {
|
||||
private AssertJThrowingCallableTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssertJThrowingCallableRules {
|
||||
private AssertJThrowingCallableRules() {}
|
||||
|
||||
static final class AssertThatThrownByIllegalArgumentException {
|
||||
@BeforeTemplate
|
||||
@@ -59,6 +61,27 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageParameters {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatIllegalArgumentException()
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessage(message, parameters);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractObjectAssert<?, ?> after(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatThrownBy(throwingCallable)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage(message, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageStartingWith {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
@@ -148,6 +171,27 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByIllegalStateExceptionHasMessageParameters {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatIllegalStateException()
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessage(message, parameters);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractObjectAssert<?, ?> after(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatThrownBy(throwingCallable)
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessage(message, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByIllegalStateExceptionHasMessageStartingWith {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
@@ -235,6 +279,27 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByNullPointerExceptionHasMessageParameters {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatNullPointerException()
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessage(message, parameters);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractObjectAssert<?, ?> after(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatThrownBy(throwingCallable)
|
||||
.isInstanceOf(NullPointerException.class)
|
||||
.hasMessage(message, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByNullPointerExceptionHasMessageStartingWith {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
@@ -284,6 +349,25 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownByIOExceptionHasMessageParameters {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings(
|
||||
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message, parameters);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractObjectAssert<?, ?> after(
|
||||
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
|
||||
return assertThatThrownBy(throwingCallable)
|
||||
.isInstanceOf(IOException.class)
|
||||
.hasMessage(message, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
static final class AssertThatThrownBy {
|
||||
@BeforeTemplate
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
@@ -321,8 +405,34 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Drop this template in favour of a generic Error Prone check which flags
|
||||
// `String.format(...)` arguments to a wide range of format methods.
|
||||
static final class AssertThatThrownByHasMessageParameters {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
|
||||
AbstractObjectAssert<?, ?> before(
|
||||
Class<? extends Throwable> exceptionType,
|
||||
ThrowingCallable throwingCallable,
|
||||
String message,
|
||||
@Repeated Object parameters) {
|
||||
return assertThatExceptionOfType(exceptionType)
|
||||
.isThrownBy(throwingCallable)
|
||||
.withMessage(message, parameters);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
AbstractObjectAssert<?, ?> after(
|
||||
Class<? extends Throwable> exceptionType,
|
||||
ThrowingCallable throwingCallable,
|
||||
String message,
|
||||
@Repeated Object parameters) {
|
||||
return assertThatThrownBy(throwingCallable)
|
||||
.isInstanceOf(exceptionType)
|
||||
.hasMessage(message, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Drop this rule in favour of a generic Error Prone check that flags `String.format(...)`
|
||||
// arguments to a wide range of format methods.
|
||||
static final class AbstractThrowableAssertHasMessage {
|
||||
@BeforeTemplate
|
||||
AbstractThrowableAssert<?, ? extends Throwable> before(
|
||||
@@ -341,8 +451,8 @@ final class AssertJThrowingCallableTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Drop this template in favour of a generic Error Prone check which flags
|
||||
// `String.format(...)` arguments to a wide range of format methods.
|
||||
// XXX: Drop this rule in favour of a generic Error Prone check that flags `String.format(...)`
|
||||
// arguments to a wide range of format methods.
|
||||
static final class AbstractThrowableAssertWithFailMessage {
|
||||
@BeforeTemplate
|
||||
AbstractThrowableAssert<?, ? extends Throwable> before(
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
@@ -27,14 +27,16 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
* Assorted Refaster templates that do not (yet) belong in one of the other classes with more
|
||||
* topical Refaster templates.
|
||||
* Assorted Refaster rules that do not (yet) belong in one of the other classes with more topical
|
||||
* Refaster rules.
|
||||
*/
|
||||
final class AssortedTemplates {
|
||||
private AssortedTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class AssortedRules {
|
||||
private AssortedRules() {}
|
||||
|
||||
/** Prefer {@link Objects#checkIndex(int, int)} over the Guava alternative. */
|
||||
static final class CheckIndex {
|
||||
@@ -44,11 +46,33 @@ final class AssortedTemplates {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
int after(int index, int size) {
|
||||
return checkIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Objects#checkIndex(int, int)} over less descriptive or more verbose alternatives.
|
||||
*
|
||||
* <p>If a custom error message is desired, consider using Guava's {@link
|
||||
* com.google.common.base.Preconditions#checkElementIndex(int, int, String)}.
|
||||
*/
|
||||
static final class CheckIndexConditional {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size) {
|
||||
checkIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
|
||||
// not allow an empty non-EnumMap to be provided.
|
||||
static final class CreateEnumMap<K extends Enum<K>, V> {
|
||||
@@ -65,14 +89,12 @@ final class AssortedTemplates {
|
||||
|
||||
static final class MapGetOrNull<K, V, L> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
V before(Map<K, V> map, L key) {
|
||||
@Nullable V before(Map<K, V> map, L key) {
|
||||
return map.getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
V after(Map<K, V> map, L key) {
|
||||
@Nullable V after(Map<K, V> map, L key) {
|
||||
return map.get(key);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +104,7 @@ final class AssortedTemplates {
|
||||
* ImmutableSet#toImmutableSet()} and produces a more compact object.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving: while the
|
||||
* original code produces a set which iterates over the elements in encounter order, the
|
||||
* original code produces a set that iterates over the elements in encounter order, the
|
||||
* replacement code iterates over the elements in enum definition order.
|
||||
*/
|
||||
// XXX: ^ Consider emitting a comment warning about this fact?
|
||||
@@ -110,15 +132,14 @@ final class AssortedTemplates {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
T after(Iterator<T> iterator, T defaultValue) {
|
||||
@Nullable T after(Iterator<T> iterator, T defaultValue) {
|
||||
return Iterators.getNext(iterator, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily repeat boolean expressions. */
|
||||
// XXX: This template captures only the simplest case. `@AlsoNegation` doesn't help. Consider
|
||||
// contributing a Refaster patch which handles the negation in the `@BeforeTemplate` more
|
||||
// XXX: This rule captures only the simplest case. `@AlsoNegation` doesn't help. Consider
|
||||
// contributing a Refaster patch, which handles the negation in the `@BeforeTemplate` more
|
||||
// intelligently.
|
||||
static final class LogicalImplication {
|
||||
@BeforeTemplate
|
||||
@@ -172,8 +193,8 @@ final class AssortedTemplates {
|
||||
* Collections#disjoint(Collection, Collection)}.
|
||||
*/
|
||||
// XXX: Other copy operations could be elided too, but these are most common after application of
|
||||
// the `DisjointSets` template defined above. If we ever introduce a generic "makes a copy"
|
||||
// stand-in, use it here.
|
||||
// the `DisjointSets` rule defined above. If we ever introduce a generic "makes a copy" stand-in,
|
||||
// use it here.
|
||||
static final class DisjointCollections<T> {
|
||||
@BeforeTemplate
|
||||
boolean before(Collection<T> collection1, Collection<T> collection2) {
|
||||
@@ -1,13 +1,15 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.math.BigDecimal;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link BigDecimal}s. */
|
||||
final class BigDecimalTemplates {
|
||||
private BigDecimalTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link BigDecimal}s. */
|
||||
@OnlineDocumentation
|
||||
final class BigDecimalRules {
|
||||
private BigDecimalRules() {}
|
||||
|
||||
/** Prefer using the constant {@link BigDecimal#ZERO} when possible. */
|
||||
static final class BigDecimalZero {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -15,15 +15,18 @@ import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with (arbitrary) collections. */
|
||||
/** Refaster rules related to expressions dealing with (arbitrary) collections. */
|
||||
// XXX: There are other Guava `Iterables` methods that should not be called if the input is known to
|
||||
// be a `Collection`. Add those here.
|
||||
final class CollectionTemplates {
|
||||
private CollectionTemplates() {}
|
||||
@OnlineDocumentation
|
||||
final class CollectionRules {
|
||||
private CollectionRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link Collection#isEmpty()} over alternatives that consult the collection's size or are
|
||||
@@ -129,31 +132,32 @@ final class CollectionTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
static final class CollectionRemoveAllFromCollectionBlock<T, S extends T> {
|
||||
static final class SetRemoveAllCollection<T, S extends T> {
|
||||
@BeforeTemplate
|
||||
void before(Collection<T> removeTo, Collection<S> elementsToRemove) {
|
||||
elementsToRemove.forEach(removeTo::remove);
|
||||
void before(Set<T> removeFrom, Collection<S> elementsToRemove) {
|
||||
elementsToRemove.forEach(removeFrom::remove);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
void before2(Collection<T> removeTo, Collection<S> elementsToRemove) {
|
||||
void before2(Set<T> removeFrom, Collection<S> elementsToRemove) {
|
||||
for (T element : elementsToRemove) {
|
||||
removeTo.remove(element);
|
||||
removeFrom.remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: This method is identical to `before2` except for the loop type. Make Refaster smarter so
|
||||
// that this is supported out of the box.
|
||||
// that this is supported out of the box. After doing so, also drop the `S extends T` type
|
||||
// constraint; ideally this check applies to any `S`.
|
||||
@BeforeTemplate
|
||||
void before3(Collection<T> removeTo, Collection<S> elementsToRemove) {
|
||||
void before3(Set<T> removeFrom, Collection<S> elementsToRemove) {
|
||||
for (S element : elementsToRemove) {
|
||||
removeTo.remove(element);
|
||||
removeFrom.remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
void after(Collection<T> removeTo, Collection<S> elementsToRemove) {
|
||||
removeTo.removeAll(elementsToRemove);
|
||||
void after(Set<T> removeFrom, Collection<S> elementsToRemove) {
|
||||
removeFrom.removeAll(elementsToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Comparator.comparing;
|
||||
@@ -19,14 +19,17 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.ToDoubleFunction;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.function.ToLongFunction;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link Comparator}s. */
|
||||
final class ComparatorTemplates {
|
||||
private ComparatorTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link Comparator}s. */
|
||||
@OnlineDocumentation
|
||||
final class ComparatorRules {
|
||||
private ComparatorRules() {}
|
||||
|
||||
/** Prefer {@link Comparator#naturalOrder()} over more complicated constructs. */
|
||||
static final class NaturalOrder<T extends Comparable<? super T>> {
|
||||
@@ -259,4 +262,36 @@ final class ComparatorTemplates {
|
||||
return Comparators.max(value1, value2, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer a method reference to {@link Comparators#min(Comparable, Comparable)} over calling
|
||||
* {@link BinaryOperator#minBy(Comparator)} with {@link Comparator#naturalOrder()}.
|
||||
*/
|
||||
static final class ComparatorsMin<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
BinaryOperator<T> before() {
|
||||
return BinaryOperator.minBy(naturalOrder());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
BinaryOperator<T> after() {
|
||||
return Comparators::min;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer a method reference to {@link Comparators#max(Comparable, Comparable)} over calling
|
||||
* {@link BinaryOperator#minBy(Comparator)} with {@link Comparator#naturalOrder()}.
|
||||
*/
|
||||
static final class ComparatorsMax<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
BinaryOperator<T> before() {
|
||||
return BinaryOperator.maxBy(naturalOrder());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
BinaryOperator<T> after() {
|
||||
return Comparators::max;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
@@ -12,10 +12,12 @@ import java.util.function.DoublePredicate;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
import java.util.stream.DoubleStream;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link DoubleStream}s. */
|
||||
final class DoubleStreamTemplates {
|
||||
private DoubleStreamTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link DoubleStream}s. */
|
||||
@OnlineDocumentation
|
||||
final class DoubleStreamRules {
|
||||
private DoubleStreamRules() {}
|
||||
|
||||
/** Don't unnecessarily call {@link Streams#concat(DoubleStream...)}. */
|
||||
static final class ConcatOneDoubleStream {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
@@ -6,10 +6,12 @@ import com.google.errorprone.refaster.annotation.AlsoNegation;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with (in)equalities. */
|
||||
final class EqualityTemplates {
|
||||
private EqualityTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with (in)equalities. */
|
||||
@OnlineDocumentation
|
||||
final class EqualityRules {
|
||||
private EqualityRules() {}
|
||||
|
||||
/** Prefer reference-based quality for enums. */
|
||||
// Primitive value comparisons are not listed, because Error Prone flags those out of the box.
|
||||
@@ -20,7 +22,7 @@ final class EqualityTemplates {
|
||||
* remaining reference-based equality checks.
|
||||
*/
|
||||
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
|
||||
// work around the issue by selecting the "largest replacements". See RefasterCheck.
|
||||
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
|
||||
@BeforeTemplate
|
||||
boolean before(T a, T b) {
|
||||
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
|
||||
@@ -34,21 +36,18 @@ final class EqualityTemplates {
|
||||
}
|
||||
|
||||
/** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */
|
||||
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsageCheck` tries to
|
||||
// achieve. If/when `MethodReferenceUsageCheck` becomes production ready, we should simply drop
|
||||
// this check.
|
||||
// XXX: Alternatively, the rule should be replaced with a plugin which also identifies cases where
|
||||
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
|
||||
// If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.
|
||||
// XXX: Alternatively, the rule should be replaced with a plugin that also identifies cases where
|
||||
// the arguments are swapped but simplification is possible anyway, by virtue of `v` being
|
||||
// non-null.
|
||||
static final class EqualsPredicate<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> before(T v) {
|
||||
return e -> v.equals(e);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> after(T v) {
|
||||
return v::equals;
|
||||
}
|
||||
@@ -71,17 +70,14 @@ final class EqualityTemplates {
|
||||
* Don't negate an equality test or use the ternary operator to compare two booleans; directly
|
||||
* test for inequality instead.
|
||||
*/
|
||||
// XXX: Replacing `a ? !b : b` with `a != b` changes semantics if both `a` and `b` are boxed
|
||||
// booleans.
|
||||
static final class Negation {
|
||||
@BeforeTemplate
|
||||
boolean before(boolean a, boolean b) {
|
||||
return Refaster.anyOf(!(a == b), a ? !b : b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a == b);
|
||||
@@ -102,17 +98,14 @@ final class EqualityTemplates {
|
||||
* Don't negate an inequality test or use the ternary operator to compare two booleans; directly
|
||||
* test for equality instead.
|
||||
*/
|
||||
// XXX: Replacing `a ? b : !b` with `a == b` changes semantics if both `a` and `b` are boxed
|
||||
// booleans.
|
||||
static final class IndirectDoubleNegation {
|
||||
@BeforeTemplate
|
||||
boolean before(boolean a, boolean b) {
|
||||
return Refaster.anyOf(!(a != b), a ? b : !b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a != b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a != b);
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
|
||||
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
|
||||
@@ -24,17 +24,19 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableListMultimap}s. */
|
||||
final class ImmutableListMultimapTemplates {
|
||||
private ImmutableListMultimapTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableListMultimap}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableListMultimapRules {
|
||||
private ImmutableListMultimapRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link ImmutableListMultimap#builder()} over the associated constructor on constructions
|
||||
* that produce a less-specific type.
|
||||
*/
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableListMultimapBuilder<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableMultimap.Builder<K, V> before() {
|
||||
@@ -70,7 +72,7 @@ final class ImmutableListMultimapTemplates {
|
||||
* alternatives.
|
||||
*/
|
||||
// XXX: One can define variants for more than one key-value pair, but at some point the builder
|
||||
// actually produces nicer code. So it's not clear we should add Refaster templates for those
|
||||
// actually produces nicer code. So it's not clear we should add Refaster rules for those
|
||||
// variants.
|
||||
static final class PairToImmutableListMultimap<K, V> {
|
||||
@BeforeTemplate
|
||||
@@ -141,8 +143,8 @@ final class ImmutableListMultimapTemplates {
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't map a a stream's elements to map entries, only to subsequently collect them into an
|
||||
* {@link ImmutableListMultimap}. The collection can be performed directly.
|
||||
* Don't map stream's elements to map entries, only to subsequently collect them into an {@link
|
||||
* ImmutableListMultimap}. The collection can be performed directly.
|
||||
*/
|
||||
abstract static class StreamOfMapEntriesToImmutableListMultimap<E, K, V> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
@@ -20,14 +20,16 @@ import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableList}s. */
|
||||
final class ImmutableListTemplates {
|
||||
private ImmutableListTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableList}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableListRules {
|
||||
private ImmutableListRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableList#builder()} over the associated constructor. */
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableListBuilder<T> {
|
||||
@BeforeTemplate
|
||||
ImmutableList.Builder<T> before() {
|
||||
@@ -154,7 +156,7 @@ final class ImmutableListTemplates {
|
||||
* communicate the immutability of the resulting list at the type level.
|
||||
*/
|
||||
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
|
||||
// this and similar Refaster templates are replaced with an Error Prone check.
|
||||
// this and similar Refaster rules are replaced with an Error Prone check.
|
||||
static final class ImmutableListOf<T> {
|
||||
@BeforeTemplate
|
||||
List<T> before() {
|
||||
@@ -194,7 +196,7 @@ final class ImmutableListTemplates {
|
||||
* Prefer {@link ImmutableList#of(Object, Object)} over alternatives that don't communicate the
|
||||
* immutability of the resulting list at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableList.builder()` usages.
|
||||
static final class ImmutableListOf2<T> {
|
||||
@BeforeTemplate
|
||||
@@ -212,7 +214,7 @@ final class ImmutableListTemplates {
|
||||
* Prefer {@link ImmutableList#of(Object, Object, Object)} over alternatives that don't
|
||||
* communicate the immutability of the resulting list at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableList.builder()` usages.
|
||||
static final class ImmutableListOf3<T> {
|
||||
@BeforeTemplate
|
||||
@@ -230,7 +232,7 @@ final class ImmutableListTemplates {
|
||||
* Prefer {@link ImmutableList#of(Object, Object, Object, Object)} over alternatives that don't
|
||||
* communicate the immutability of the resulting list at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableList.builder()` usages.
|
||||
static final class ImmutableListOf4<T> {
|
||||
@BeforeTemplate
|
||||
@@ -248,7 +250,7 @@ final class ImmutableListTemplates {
|
||||
* Prefer {@link ImmutableList#of(Object, Object, Object, Object, Object)} over alternatives that
|
||||
* don't communicate the immutability of the resulting list at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableList.builder()` usages.
|
||||
static final class ImmutableListOf5<T> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
@@ -21,14 +21,16 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableMap}s. */
|
||||
final class ImmutableMapTemplates {
|
||||
private ImmutableMapTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableMap}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableMapRules {
|
||||
private ImmutableMapRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableMap#builder()} over the associated constructor. */
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableMapBuilder<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableMap.Builder<K, V> before() {
|
||||
@@ -135,7 +137,7 @@ final class ImmutableMapTemplates {
|
||||
abstract V valueFunction(@MayOptionallyUse E element);
|
||||
|
||||
// XXX: We could add variants in which the entry is created some other way, but we have another
|
||||
// rule which covers canonicalization to `Map.entry`.
|
||||
// rule that covers canonicalization to `Map.entry`.
|
||||
@BeforeTemplate
|
||||
ImmutableMap<K, V> before(Stream<E> stream) {
|
||||
return stream
|
||||
@@ -192,7 +194,7 @@ final class ImmutableMapTemplates {
|
||||
|
||||
// XXX: Instead of `Map.Entry::getKey` we could also match `e -> e.getKey()`. But for some
|
||||
// reason Refaster doesn't handle that case. This doesn't matter if we roll out use of
|
||||
// `MethodReferenceUsageCheck`. Same observation applies to a lot of other Refaster checks.
|
||||
// `MethodReferenceUsage`. Same observation applies to a lot of other Refaster checks.
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NullAway")
|
||||
ImmutableMap<K, V2> before(Map<K, V1> map) {
|
||||
@@ -313,7 +315,7 @@ final class ImmutableMapTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Add a template for this:
|
||||
// XXX: Add a rule for this:
|
||||
// Maps.transformValues(streamOfEntries.collect(groupBy(fun)), ImmutableMap::copyOf)
|
||||
// ->
|
||||
// streamOfEntries.collect(groupBy(fun, toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)))
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
@@ -13,14 +13,16 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableMultiset}s. */
|
||||
final class ImmutableMultisetTemplates {
|
||||
private ImmutableMultisetTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableMultiset}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableMultisetRules {
|
||||
private ImmutableMultisetRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableMultiset#builder()} over the associated constructor. */
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableMultisetBuilder<T> {
|
||||
@BeforeTemplate
|
||||
ImmutableMultiset.Builder<T> before() {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSetMultimap.flatteningToImmutableSetMultimap;
|
||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
||||
@@ -21,14 +21,16 @@ import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableSetMultimap}s. */
|
||||
final class ImmutableSetMultimapTemplates {
|
||||
private ImmutableSetMultimapTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableSetMultimap}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableSetMultimapRules {
|
||||
private ImmutableSetMultimapRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableSetMultimap#builder()} over the associated constructor. */
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableSetMultimapBuilder<K, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableSetMultimap.Builder<K, V> before() {
|
||||
@@ -56,7 +58,7 @@ final class ImmutableSetMultimapTemplates {
|
||||
|
||||
/** Prefer {@link ImmutableSetMultimap#of(Object, Object)} over more contrived alternatives. */
|
||||
// XXX: One can define variants for more than one key-value pair, but at some point the builder
|
||||
// actually produces nicer code. So it's not clear we should add Refaster templates for those
|
||||
// actually produces nicer code. So it's not clear we should add Refaster rules for those
|
||||
// variants.
|
||||
static final class PairToImmutableSetMultimap<K, V> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
@@ -17,14 +17,16 @@ import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableSet}s. */
|
||||
final class ImmutableSetTemplates {
|
||||
private ImmutableSetTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableSet}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableSetRules {
|
||||
private ImmutableSetRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableSet#builder()} over the associated constructor. */
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableSetBuilder<T> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet.Builder<T> before() {
|
||||
@@ -104,7 +106,7 @@ final class ImmutableSetTemplates {
|
||||
* communicate the immutability of the resulting set at the type level.
|
||||
*/
|
||||
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
|
||||
// this and similar Refaster templates are replaced with an Error Prone check.
|
||||
// this and similar Refaster rules are replaced with an Error Prone check.
|
||||
static final class ImmutableSetOf<T> {
|
||||
@BeforeTemplate
|
||||
Set<T> before() {
|
||||
@@ -142,7 +144,7 @@ final class ImmutableSetTemplates {
|
||||
* Prefer {@link ImmutableSet#of(Object, Object)} over alternatives that don't communicate the
|
||||
* immutability of the resulting set at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableSet.builder()` usages.
|
||||
static final class ImmutableSetOf2<T> {
|
||||
@BeforeTemplate
|
||||
@@ -160,7 +162,7 @@ final class ImmutableSetTemplates {
|
||||
* Prefer {@link ImmutableSet#of(Object, Object, Object)} over alternatives that don't communicate
|
||||
* the immutability of the resulting set at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableSet.builder()` usages.
|
||||
static final class ImmutableSetOf3<T> {
|
||||
@BeforeTemplate
|
||||
@@ -178,7 +180,7 @@ final class ImmutableSetTemplates {
|
||||
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object)} over alternatives that don't
|
||||
* communicate the immutability of the resulting set at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableSet.builder()` usages.
|
||||
static final class ImmutableSetOf4<T> {
|
||||
@BeforeTemplate
|
||||
@@ -196,7 +198,7 @@ final class ImmutableSetTemplates {
|
||||
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object, Object)} over alternatives that
|
||||
* don't communicate the immutability of the resulting set at the type level.
|
||||
*/
|
||||
// XXX: Consider writing an Error Prone check which also flags straightforward
|
||||
// XXX: Consider writing an Error Prone check that also flags straightforward
|
||||
// `ImmutableSet.builder()` usages.
|
||||
static final class ImmutableSetOf5<T> {
|
||||
@BeforeTemplate
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
|
||||
import static java.util.Comparator.naturalOrder;
|
||||
@@ -13,10 +13,12 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableSortedMap}s. */
|
||||
final class ImmutableSortedMapTemplates {
|
||||
private ImmutableSortedMapTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableSortedMap}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableSortedMapRules {
|
||||
private ImmutableSortedMapRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableSortedMap#orderedBy(Comparator)} over the associated constructor. */
|
||||
static final class ImmutableSortedMapBuilder<K, V> {
|
||||
@@ -35,8 +37,8 @@ final class ImmutableSortedMapTemplates {
|
||||
* Prefer {@link ImmutableSortedMap#naturalOrder()} over the alternative that requires explicitly
|
||||
* providing the {@link Comparator}.
|
||||
*/
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableSortedMapNaturalOrderBuilder<K extends Comparable<? super K>, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableSortedMap.Builder<K, V> before() {
|
||||
@@ -53,8 +55,8 @@ final class ImmutableSortedMapTemplates {
|
||||
* Prefer {@link ImmutableSortedMap#reverseOrder()} over the alternative that requires explicitly
|
||||
* providing the {@link Comparator}.
|
||||
*/
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
|
||||
// we can do about that?
|
||||
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
|
||||
// https://github.com/google/error-prone/pull/2706.
|
||||
static final class ImmutableSortedMapReverseOrderBuilder<K extends Comparable<? super K>, V> {
|
||||
@BeforeTemplate
|
||||
ImmutableSortedMap.Builder<K, V> before() {
|
||||
@@ -82,7 +84,7 @@ final class ImmutableSortedMapTemplates {
|
||||
|
||||
/** Prefer {@link ImmutableSortedMap#of(Object, Object)} over more contrived alternatives. */
|
||||
// XXX: One can define variants for more than one key-value pair, but at some point the builder
|
||||
// actually produces nicer code. So it's not clear we should add Refaster templates for those
|
||||
// actually produces nicer code. So it's not clear we should add Refaster rules for those
|
||||
// variants.
|
||||
// XXX: We could also rewrite builders with non-natural orders, but that would affect
|
||||
// `ImmutableSortedMap#comparator()`.
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSortedMultiset.toImmutableSortedMultiset;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
@@ -15,10 +15,12 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableSortedMultiset}s. */
|
||||
final class ImmutableSortedMultisetTemplates {
|
||||
private ImmutableSortedMultisetTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableSortedMultiset}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableSortedMultisetRules {
|
||||
private ImmutableSortedMultisetRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link ImmutableSortedMultiset#orderedBy(Comparator)} over the associated constructor.
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
@@ -15,10 +15,12 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link ImmutableSortedSet}s. */
|
||||
final class ImmutableSortedSetTemplates {
|
||||
private ImmutableSortedSetTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link ImmutableSortedSet}s. */
|
||||
@OnlineDocumentation
|
||||
final class ImmutableSortedSetRules {
|
||||
private ImmutableSortedSetRules() {}
|
||||
|
||||
/** Prefer {@link ImmutableSortedSet#orderedBy(Comparator)} over the associated constructor. */
|
||||
static final class ImmutableSortedSetBuilder<T> {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
@@ -12,10 +12,12 @@ import java.util.function.IntPredicate;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link IntStream}s. */
|
||||
final class IntStreamTemplates {
|
||||
private IntStreamTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link IntStream}s. */
|
||||
@OnlineDocumentation
|
||||
final class IntStreamRules {
|
||||
private IntStreamRules() {}
|
||||
|
||||
/** Prefer {@link IntStream#range(int, int)} over the more contrived alternative. */
|
||||
static final class IntStreamClosedOpenRange {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
@@ -8,10 +8,12 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to JUnit expressions and statements. */
|
||||
final class JUnitTemplates {
|
||||
private JUnitTemplates() {}
|
||||
/** Refaster rules related to JUnit expressions and statements. */
|
||||
@OnlineDocumentation
|
||||
final class JUnitRules {
|
||||
private JUnitRules() {}
|
||||
|
||||
/** Prefer statically imported {@link Arguments#arguments} over {@link Arguments#of} calls. */
|
||||
static final class ArgumentsEnumeration<T> {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
@@ -12,10 +12,12 @@ import java.util.function.LongPredicate;
|
||||
import java.util.function.LongUnaryOperator;
|
||||
import java.util.stream.LongStream;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link LongStream}s. */
|
||||
final class LongStreamTemplates {
|
||||
private LongStreamTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link LongStream}s. */
|
||||
@OnlineDocumentation
|
||||
final class LongStreamRules {
|
||||
private LongStreamRules() {}
|
||||
|
||||
/** Prefer {@link LongStream#range(long, long)} over the more contrived alternative. */
|
||||
static final class LongStreamClosedOpenRange {
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Comparator.comparing;
|
||||
@@ -14,10 +14,12 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link Map.Entry} instances. */
|
||||
final class MapEntryTemplates {
|
||||
private MapEntryTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link Map.Entry} instances. */
|
||||
@OnlineDocumentation
|
||||
final class MapEntryRules {
|
||||
private MapEntryRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link Map#entry(Object, Object)} over alternative ways to create an immutable map
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -10,10 +10,12 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.verification.VerificationMode;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to Mockito expressions and statements. */
|
||||
final class MockitoTemplates {
|
||||
private MockitoTemplates() {}
|
||||
/** Refaster rules related to Mockito expressions and statements. */
|
||||
@OnlineDocumentation
|
||||
final class MockitoRules {
|
||||
private MockitoRules() {}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mockito#never()}} over explicitly specifying that the associated invocation must
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
@@ -7,11 +7,13 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link Multimap}s. */
|
||||
final class MultimapTemplates {
|
||||
private MultimapTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link Multimap}s. */
|
||||
@OnlineDocumentation
|
||||
final class MultimapRules {
|
||||
private MultimapRules() {}
|
||||
|
||||
/** Prefer {@link Multimap#keySet()} over more contrived alternatives. */
|
||||
static final class MultimapKeySet<K, V> {
|
||||
@@ -48,8 +50,7 @@ final class MultimapTemplates {
|
||||
*/
|
||||
static final class MultimapGet<K, V> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
@Nullable Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
return Refaster.anyOf(multimap.asMap(), Multimaps.asMap(multimap)).get(key);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
@@ -9,10 +9,39 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with (possibly) null values. */
|
||||
final class NullTemplates {
|
||||
private NullTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with (possibly) null values. */
|
||||
@OnlineDocumentation
|
||||
final class NullRules {
|
||||
private NullRules() {}
|
||||
|
||||
/** Prefer the {@code ==} operator over {@link Objects#isNull(Object)}. */
|
||||
static final class IsNull {
|
||||
@BeforeTemplate
|
||||
boolean before(@Nullable Object object) {
|
||||
return Objects.isNull(object);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(@Nullable Object object) {
|
||||
return object == null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer the {@code !=} operator over {@link Objects#nonNull(Object)}. */
|
||||
static final class IsNotNull {
|
||||
@BeforeTemplate
|
||||
boolean before(@Nullable Object object) {
|
||||
return Objects.nonNull(object);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
boolean after(@Nullable Object object) {
|
||||
return object != null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Objects#requireNonNullElse(Object, Object)} over the Guava alternative. */
|
||||
// XXX: This rule is not valid in case `second` is `@Nullable`: in that case the Guava variant
|
||||
@@ -33,13 +62,11 @@ final class NullTemplates {
|
||||
/** Prefer {@link Objects#isNull(Object)} over the equivalent lambda function. */
|
||||
static final class IsNullFunction<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> before() {
|
||||
return o -> o == null;
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> after() {
|
||||
return Objects::isNull;
|
||||
}
|
||||
@@ -48,13 +75,11 @@ final class NullTemplates {
|
||||
/** Prefer {@link Objects#nonNull(Object)} over the equivalent lambda function. */
|
||||
static final class NonNullFunction<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> before() {
|
||||
return o -> o != null;
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Predicate<T> after() {
|
||||
return Objects::nonNull;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
|
||||
@@ -9,17 +9,20 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with {@link Optional}s. */
|
||||
final class OptionalTemplates {
|
||||
private OptionalTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with {@link Optional}s. */
|
||||
@OnlineDocumentation
|
||||
final class OptionalRules {
|
||||
private OptionalRules() {}
|
||||
|
||||
static final class OptionalOfNullable<T> {
|
||||
// XXX: Refaster should be smart enough to also rewrite occurrences in which there are
|
||||
@@ -77,17 +80,15 @@ final class OptionalTemplates {
|
||||
}
|
||||
|
||||
/** Prefer {@link Optional#orElseThrow()} over the less explicit {@link Optional#get()}. */
|
||||
// XXX: This template is analogous to `OptionalOrElseThrow` above. Arguably this is its
|
||||
// generalization. If/when Refaster is extended to understand this, delete the template above.
|
||||
// XXX: This rule is analogous to `OptionalOrElseThrow` above. Arguably this is its
|
||||
// generalization. If/when Refaster is extended to understand this, delete the rule above.
|
||||
static final class OptionalOrElseThrowMethodReference<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Function<Optional<T>, T> before() {
|
||||
return Optional::get;
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("NoFunctionalReturnType")
|
||||
Function<Optional<T>, T> after() {
|
||||
return Optional::orElseThrow;
|
||||
}
|
||||
@@ -113,7 +114,7 @@ final class OptionalTemplates {
|
||||
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
|
||||
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Maybe our RefasterCheck should test `compilesWithFix`?
|
||||
// Maybe our `Refaster` checker should test `compilesWithFix`?
|
||||
abstract static class TernaryOperatorOptionalPositiveFiltering<T> {
|
||||
@Placeholder
|
||||
abstract boolean test(T value);
|
||||
@@ -133,7 +134,7 @@ final class OptionalTemplates {
|
||||
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
|
||||
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Maybe our RefasterCheck should test `compilesWithFix`?
|
||||
// Maybe our `Refaster` checker should test `compilesWithFix`?
|
||||
abstract static class TernaryOperatorOptionalNegativeFiltering<T> {
|
||||
@Placeholder
|
||||
abstract boolean test(T value);
|
||||
@@ -154,7 +155,7 @@ final class OptionalTemplates {
|
||||
* Prefer {@link Optional#filter(Predicate)} over {@link Optional#map(Function)} when converting
|
||||
* an {@link Optional} to a boolean.
|
||||
*/
|
||||
abstract static class MapOptionalToBoolean<T> {
|
||||
static final class MapOptionalToBoolean<T> {
|
||||
@BeforeTemplate
|
||||
boolean before(Optional<T> optional, Function<? super T, Boolean> predicate) {
|
||||
return optional.map(predicate).orElse(Refaster.anyOf(false, Boolean.FALSE));
|
||||
@@ -167,7 +168,7 @@ final class OptionalTemplates {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Optional#map} over a {@link Optional#flatMap} which wraps the result of a
|
||||
* Prefer {@link Optional#map} over a {@link Optional#flatMap} that wraps the result of a
|
||||
* transformation in an {@link Optional}; the former operation transforms {@code null} to {@link
|
||||
* Optional#empty()}.
|
||||
*/
|
||||
@@ -239,9 +240,16 @@ final class OptionalTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
/** Within a stream's map operation unconditional {@link Optional#get()} calls can be avoided. */
|
||||
// XXX: An alternative approach is to `.flatMap(Optional::stream)`. That may be a bit longer, but
|
||||
// yield nicer code. Think about it.
|
||||
/**
|
||||
* Within a stream's map operation unconditional {@link Optional#orElseThrow()} calls can be
|
||||
* avoided.
|
||||
*
|
||||
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving. The
|
||||
* original code throws an exception if the mapping operation does not produce a value, while the
|
||||
* replacement does not.
|
||||
*/
|
||||
// XXX: An alternative approach is to use `.flatMap(Optional::stream)`. That may be a bit longer,
|
||||
// but yields nicer code. Think about it.
|
||||
abstract static class StreamMapToOptionalGet<T, S> {
|
||||
@Placeholder
|
||||
abstract Optional<S> toOptionalFunction(@MayOptionallyUse T element);
|
||||
@@ -309,8 +317,9 @@ final class OptionalTemplates {
|
||||
}
|
||||
|
||||
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
|
||||
abstract static class OptionalOrOtherOptional<T> {
|
||||
static final class OptionalOrOtherOptional<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedOptionals" /* Auto-fix for the `NestedOptionals` check. */)
|
||||
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
|
||||
// XXX: Note that rewriting the first and third variant will change the code's behavior if
|
||||
// `optional2` has side-effects.
|
||||
@@ -326,6 +335,62 @@ final class OptionalTemplates {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary operations on an {@link Optional} that ultimately result in that very same
|
||||
* {@link Optional}.
|
||||
*/
|
||||
static final class OptionalIdentity<T> {
|
||||
@BeforeTemplate
|
||||
Optional<T> before(Optional<T> optional, Comparator<? super T> comparator) {
|
||||
return Refaster.anyOf(
|
||||
optional.stream().findFirst(),
|
||||
optional.stream().findAny(),
|
||||
optional.stream().min(comparator),
|
||||
optional.stream().max(comparator));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<T> after(Optional<T> optional) {
|
||||
return optional;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when filtering a value of the
|
||||
* former type.
|
||||
*/
|
||||
static final class OptionalFilter<T> {
|
||||
@BeforeTemplate
|
||||
Optional<T> before(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return Refaster.anyOf(
|
||||
optional.stream().filter(predicate).findFirst(),
|
||||
optional.stream().filter(predicate).findAny());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<T> after(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return optional.filter(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when mapping a value of the
|
||||
* former type.
|
||||
*/
|
||||
// XXX: If `StreamMapFirst` also simplifies `.findAny()` expressions, then this rule can be
|
||||
// dropped in favour of `StreamMapFirst` and `OptionalIdentity`.
|
||||
static final class OptionalMap<S, T> {
|
||||
@BeforeTemplate
|
||||
Optional<? extends T> before(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.stream().map(function).findAny();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<? extends T> after(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.map(function);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Add a rule for:
|
||||
// `optional.flatMap(x -> pred(x) ? Optional.empty() : Optional.of(x))` and variants.
|
||||
// (Maybe canonicalize the inner expression. Maybe we rewrite already.)
|
||||
@@ -0,0 +1,176 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkPositionIndex;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to statements dealing with {@link Preconditions}. */
|
||||
@OnlineDocumentation
|
||||
final class PreconditionsRules {
|
||||
private PreconditionsRules() {}
|
||||
|
||||
/** Prefer {@link Preconditions#checkArgument(boolean)} over more verbose alternatives. */
|
||||
static final class CheckArgument {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
if (condition) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
checkArgument(!condition);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkArgument(boolean, Object)} over more verbose alternatives. */
|
||||
static final class CheckArgumentWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
if (condition) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
checkArgument(!condition, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkElementIndex(int, int, String)} over less descriptive or more
|
||||
* verbose alternatives.
|
||||
*
|
||||
* <p>Note that the two-argument {@link Preconditions#checkElementIndex(int, int)} is better
|
||||
* replaced with {@link java.util.Objects#checkIndex(int, int)}.
|
||||
*/
|
||||
static final class CheckElementIndexWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size, String message) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IndexOutOfBoundsException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size, String message) {
|
||||
checkElementIndex(index, size, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkNotNull(Object)} over more verbose alternatives. */
|
||||
static final class CheckNotNull<T> {
|
||||
@BeforeTemplate
|
||||
void before(T object) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(T object) {
|
||||
checkNotNull(object);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkNotNull(Object, Object)} over more verbose alternatives. */
|
||||
static final class CheckNotNullWithMessage<T> {
|
||||
@BeforeTemplate
|
||||
void before(T object, String message) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(T object, String message) {
|
||||
checkNotNull(object, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkPositionIndex(int, int)} over less descriptive or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class CheckPositionIndex {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size) {
|
||||
if (index < 0 || index > size) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size) {
|
||||
checkPositionIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkPositionIndex(int, int, String)} over less descriptive or more
|
||||
* verbose alternatives.
|
||||
*/
|
||||
static final class CheckPositionIndexWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size, String message) {
|
||||
if (index < 0 || index > size) {
|
||||
throw new IndexOutOfBoundsException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size, String message) {
|
||||
checkPositionIndex(index, size, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkState(boolean)} over more verbose alternatives. */
|
||||
static final class CheckState {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
if (condition) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
checkState(!condition);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkState(boolean, Object)} over more verbose alternatives. */
|
||||
static final class CheckStateWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
if (condition) {
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
checkState(!condition, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
package tech.picnic.errorprone.refastertemplates;
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to expressions dealing with primitives. */
|
||||
final class PrimitiveTemplates {
|
||||
private PrimitiveTemplates() {}
|
||||
/** Refaster rules related to expressions dealing with primitives. */
|
||||
@OnlineDocumentation
|
||||
final class PrimitiveRules {
|
||||
private PrimitiveRules() {}
|
||||
|
||||
/** Avoid contrived ways of expressing the "less than" relationship. */
|
||||
static final class LessThan {
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a >= b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a >= b);
|
||||
@@ -28,11 +25,6 @@ final class PrimitiveTemplates {
|
||||
|
||||
/** Avoid contrived ways of expressing the "less than or equal to" relationship. */
|
||||
static final class LessThanOrEqualTo {
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a > b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a > b);
|
||||
@@ -46,11 +38,6 @@ final class PrimitiveTemplates {
|
||||
|
||||
/** Avoid contrived ways of expressing the "greater than" relationship. */
|
||||
static final class GreaterThan {
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a <= b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a <= b);
|
||||
@@ -64,11 +51,6 @@ final class PrimitiveTemplates {
|
||||
|
||||
/** Avoid contrived ways of expressing the "greater than or equal to" relationship. */
|
||||
static final class GreaterThanOrEqualTo {
|
||||
@BeforeTemplate
|
||||
boolean before(long a, long b) {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
boolean before(double a, double b) {
|
||||
return !(a < b);
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user