Compare commits

...

52 Commits

Author SHA1 Message Date
japborst
525df33e42 gdejong/docgen -> gdejong/docgen_old 2022-10-10 21:06:24 +02:00
japborst
368f70fc98 Rename refastertemplates -> refasterrules 2022-10-10 21:05:24 +02:00
japborst
ec023cd8cf Remove trailing space for code blocks 2022-09-27 16:49:02 +02:00
Rick Ossendrijver
9ae3c70dff Update .gitignore, fix typo, and sort links in README 2022-09-27 16:15:42 +02:00
japborst
f781c85143 Fix GitHub source path 2022-09-27 11:06:44 +02:00
japborst
d1054bfb8b Deploy to GH pages 2022-09-27 10:20:29 +02:00
japborst
ac86140420 Reduce # regex 2022-09-27 10:20:29 +02:00
japborst
1ed4892d34 Generate output lines 2022-09-27 10:20:29 +02:00
japborst
8ee643e9b9 Style summary, and support explanations 2022-09-27 10:20:29 +02:00
japborst
272245b9d4 Exclude generated files 2022-09-27 10:20:29 +02:00
Gijs de Jong
d8009e9c26 [WIP] Initial regex extraction + docgen 2022-09-27 10:20:29 +02:00
japborst
d7f1fc6cff Use absolute path for Picnic logo 2022-09-27 10:20:19 +02:00
japborst
8760f7f31f A few SEO improvements 2022-09-24 13:43:42 +02:00
japborst
81ddfdb6fb Add Picnic logo; small improvements 2022-09-21 11:50:41 +02:00
japborst
ccf3ce4962 Bootstrap documentation website 2022-09-21 11:33:41 +02:00
Svava Bjarnadóttir
3874ca9be2 Introduce OptionalIdentity Refaster template (#245) 2022-09-19 21:20:50 +02:00
Jelmer Borst
84ba3946d9 Introduce {CONTRIBUTING,LICENSE,README}.md and Error Prone Support's logo (#212) 2022-09-19 08:50:08 +02:00
Picnic-Bot
b675ff680f Upgrade Error Prone 2.14.0 -> 2.15.0 (#179)
See:
- https://github.com/google/error-prone/releases/tag/v2.15.0
- https://github.com/google/error-prone/compare/v2.14.0...v2.15.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.14.0-picnic-2...v2.15.0-picnic-3
2022-09-18 15:25:18 +02:00
Shang Xiang
8e7d04a24c Introduce AssertJComparableTemplates and AssertJPrimitiveTemplates (#225) 2022-09-18 14:59:18 +02:00
Rick Ossendrijver
bfc951b61f Introduce IsCharacter matcher for use by Refaster templates (#237)
This new matcher is used to improve the `AssertThatIsOdd` and
`AssertThatIsEven` Refaster templates.

While there, apply assorted semi-related test improvements.
2022-09-18 14:39:25 +02:00
Picnic-Bot
b30562bbd8 Upgrade maven-jar-plugin 3.2.2 -> 3.3.0 (#242)
See:
- https://github.com/apache/maven-jar-plugin/releases/tag/maven-jar-plugin-3.3.0
- https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.2.2...maven-jar-plugin-3.3.0
2022-09-17 13:10:30 +02:00
Picnic-Bot
9be85204ae Upgrade versions-maven-plugin 2.11.0 -> 2.12.0 (#226)
See:
- https://github.com/mojohaus/versions-maven-plugin/releases/tag/versions-maven-plugin-2.12.0
- https://github.com/mojohaus/versions-maven-plugin/compare/versions-maven-plugin-2.11.0...versions-maven-plugin-2.12.0
2022-09-16 20:29:26 +02:00
Picnic-Bot
6fbf1b0cb2 Upgrade New Relic Java Agent 7.9.0 -> 7.10.0 (#241)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.10.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.9.0...v7.10.0
2022-09-16 09:06:18 +02:00
Picnic-Bot
5a428e6e29 Upgrade Spring 5.3.22 -> 5.3.23 (#240)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.23
- https://github.com/spring-projects/spring-framework/compare/v5.3.22...v5.3.23
2022-09-16 08:52:40 +02:00
Picnic-Bot
71163c0061 Upgrade Project Reactor 2020.0.22 -> 2020.0.23 (#239)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.23
- https://github.com/reactor/reactor/compare/2020.0.22...2020.0.23
2022-09-15 21:30:56 +02:00
Picnic-Bot
880be0dbdd Upgrade NullAway 0.10.0 -> 0.10.1 (#238)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.0...v0.10.1
2022-09-15 21:08:43 +02:00
Stephan Schroevers
62fe10f2cd Prefer Mono#fromSupplier over Mono#fromCallable where possible (#232)
This rule is implemented using a Refaster template, relying on the new 
`ThrowsCheckedException` matcher.

While there, introduce `AbstractTestChecker` to simplify the test setup for
Refaster `Matcher`s. This base class flags all `ExpressionTree`s matched by the
`Matcher` under test.
2022-09-15 09:42:16 +02:00
Nathan Kooij
f7ec28368a Drop or replace references to Travis CI (#236)
- Update Maven's `ciManagement` section to refer to GitHub Actions.
- Drop an obsolete "wish list" entry.
2022-09-14 07:38:22 +02:00
Jeanderson Barros Candido
e34c2baf7c Prefer simple null reference check over calling Objects#{isNull,nonNull} (#228) 2022-09-10 14:37:54 +02:00
Picnic-Bot
184ba8af51 Upgrade NullAway 0.9.10 -> 0.10.0 (#231)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.10...v0.10.0
2022-09-10 14:04:36 +02:00
Stephan Schroevers
63c3aa8259 Improve the RedundantStringConversion check (#193)
- Describe the set of well-known string conversion methods more
  concisely.
- Extend said set to include all relevant `String#valueOf` overloads.
2022-09-10 13:11:44 +02:00
Picnic-Bot
7daabeee48 Upgrade Mockito 4.7.0 -> 4.8.0 (#230)
See:
- https://github.com/mockito/mockito/releases/tag/v4.8.0
- https://github.com/mockito/mockito/compare/v4.7.0...v4.8.0
2022-09-09 10:14:20 +02:00
Picnic-Bot
0acfd8a723 Upgrade Immutables Annotations 2.9.1 -> 2.9.2 (#229)
See:
- https://github.com/immutables/immutables/releases/tag/2.9.2
- https://github.com/immutables/immutables/compare/2.9.1r...2.9.2
2022-09-09 09:04:13 +02:00
Vincent Koeman
000fcefe92 Have RequestMappingAnnotation recognize @RequestPart parameters (#227) 2022-09-08 15:25:46 +02:00
Rick Ossendrijver
1e3ad0fe32 Fix typo in pom.xml (#222) 2022-09-08 12:49:45 +02:00
Stephan Schroevers
b88a668819 Reduce GitHub Actions build workflow permissions (#221) 2022-09-08 08:54:43 +02:00
Stephan Schroevers
4c8e125dcb Drop the dependency on com.google.errorprone:javac (#197)
This new setup matches the upstream Error Prone build configuration and
simplifies development against JDK 11+ internal APIs.
2022-09-05 16:11:06 +02:00
Picnic-Bot
4ab5dc4f32 Upgrade Jackson 2.13.3 -> 2.13.4 (#220)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.13.4
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.3...jackson-bom-2.13.4
2022-09-05 09:11:41 +02:00
Picnic-Bot
7c667334cc Upgrade Checker Framework Annotations 3.24.0 -> 3.25.0 (#219)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.25.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.24.0...checker-framework-3.25.0
2022-09-03 13:31:57 +02:00
Picnic-Bot
b4b2afd130 Upgrade NullAway 0.9.9 -> 0.9.10 (#217)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.9...v0.9.10
2022-09-01 08:06:51 +02:00
Picnic-Bot
4445c93f6e Upgrade errorprone-slf4j 0.1.13 -> 0.1.15 (#218)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.14
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.15
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.13...v0.1.15
2022-09-01 08:00:59 +02:00
Picnic-Bot
5a7d7ff89b Upgrade Checkstyle 10.3.2 -> 10.3.3 (#216)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.3
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.2...checkstyle-10.3.3
2022-08-30 10:02:17 +02:00
Picnic-Bot
7ef75e8f07 Upgrade maven-checkstyle-plugin 3.1.2 -> 3.2.0 (#215)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.1.2%20AND%20fixVersion%20%3C%3D%203.2.0
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.1.2...maven-checkstyle-plugin-3.2.0
2022-08-25 09:26:59 +02:00
Rick Ossendrijver
a1f2418805 Introduce release.yml to improve GitHub release notes generation (#213) 2022-08-24 16:31:15 +02:00
Picnic-Bot
39bfcc75ca Upgrade Immutables 2.9.0 -> 2.9.1 (#210)
See:
- https://github.com/immutables/immutables/releases/tag/2.9.1
- https://github.com/immutables/immutables/compare/2.9.0...2.9.1
2022-08-24 09:33:53 +02:00
Stephan Schroevers
753cdce29e [maven-release-plugin] prepare for next development iteration 2022-08-23 08:33:02 +02:00
Stephan Schroevers
36654883e5 [maven-release-plugin] prepare release v0.2.0 2022-08-23 08:33:02 +02:00
Picnic-Bot
d5372934ec Upgrade pitest-maven-plugin 1.9.4 -> 1.9.5 (#211)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.5
- https://github.com/hcoles/pitest/compare/1.9.4...1.9.5
2022-08-23 07:55:01 +02:00
Stephan Schroevers
f810530599 Fix several RxJava2Adapter Refaster templates (#205)
This reverts some changes from d2bbee3ed9
and instead drops some invalid `Flowable#compose` and `Flux#transform`
rewrite rules.
2022-08-22 11:05:24 +02:00
Stephan Schroevers
6928381403 Drop unnecessary dependency declarations (#208) 2022-08-22 10:49:40 +02:00
Stephan Schroevers
5657a48552 Prefer String#valueOf over Objects#toString (#192)
While there, drop obsolete `NoFunctionalReturnType` warning suppressions.
2022-08-22 10:29:23 +02:00
Stephan Schroevers
50aaf77a9e Enable nohttp-checkstyle (#206)
While this Checkstyle configuration only flags `http://` usages in
Maven-managed source files (thus not in e.g. `pom.xml` or `README.md`
files), this is a low-effort improvement.
2022-08-22 09:39:19 +02:00
102 changed files with 3400 additions and 380 deletions

29
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
changelog:
exclude:
labels:
- "ignore-changelog"
categories:
- title: ":rocket: New Error Prone checks and Refaster templates"
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:
- "*"

View File

@@ -2,8 +2,10 @@ name: Build and verify
on:
pull_request:
push:
branches:
- 'master'
branches: [$default-branch]
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-22.04

46
.github/workflows/deploy-website.yaml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Update `error-prone.picnic.tech` website contents
on:
push:
branches:
- 'gdejong/docgen_old'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.0.2
- name: Set up JDK
uses: actions/setup-java@v3.4.1
with:
java-version: 11.0.16
distribution: temurin
cache: maven
- name: Configure Github Pages
uses: actions/configure-pages@v2.0.0
- name: Generate documentation
run: bash ./generate-docs.sh
- name: Build website with Jekyll
uses: actions/jekyll-build-pages@v1.0.5
with:
source: website/
destination: ./_site
- name: Upload website artifact
uses: actions/upload-pages-artifact@v1.0.3
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-22.04
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1.0.9

View File

@@ -2,6 +2,7 @@
-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
@@ -9,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
View 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 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) 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
View 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
View File

@@ -0,0 +1,231 @@
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="logo.svg">
<img alt="Error Prone Support logo" src="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]
[![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] -------------------------------------------------------------
[WARNING] COMPILATION WARNING :
[INFO] -------------------------------------------------------------
[WARNING] Example.java:[9,34] [tech.picnic.errorprone.refasterrules.BigDecimalTemplates.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() {'?
[INFO] 2 warnings
[INFO] -------------------------------------------------------------
...
```
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/BigDecimalTemplates.java
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/

162
docgen/pom.xml Normal file
View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2011 The Error Prone Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.2.1-SNAPSHOT</version>
</parent>
<name>Picnic :: Error Prone Support :: DocGen</name>
<artifactId>error_prone_docgen</artifactId>
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${version.auto-value}</version>
</path>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>${version.auto-service}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.mustache</include>
</includes>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>error-prone-contrib</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error_prone_docgen_processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.30</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.82</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<version>${version.auto-value}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.spullara.mustache.java</groupId>
<artifactId>compiler</artifactId>
<version>0.9.10</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>run-annotation-processor</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>site</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.google.errorprone.DocGenTool</mainClass>
<arguments>
<argument>
-bug_patterns=${basedir}/../error-prone-contrib/target/generated-sources/annotations/bugPatterns.txt
</argument>
<argument>-docs_repository=${basedir}/target/generated-wiki/</argument>
<argument>-explanations=${basedir}/../docs/bugpatterns/</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2014 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.LineProcessor;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import javax.annotation.Nullable;
/**
* Reads each line of the bugpatterns.txt tab-delimited data file, and generates a GitHub Jekyll
* page for each one.
*
* @author alexeagle@google.com (Alex Eagle)
*/
class BugPatternFileGenerator implements LineProcessor<List<BugPatternInstance>> {
private final Path outputDir;
private final Path explanationDir;
private final List<BugPatternInstance> result;
private final Function<BugPatternInstance, SeverityLevel> severityRemapper;
/** The base url for links to bugpatterns. */
@Nullable private final String baseUrl;
public BugPatternFileGenerator(
Path bugpatternDir,
Path explanationDir,
String baseUrl,
Function<BugPatternInstance, SeverityLevel> severityRemapper) {
this.outputDir = bugpatternDir;
this.explanationDir = explanationDir;
this.severityRemapper = severityRemapper;
this.baseUrl = baseUrl;
result = new ArrayList<>();
}
@Override
public boolean processLine(String line) throws IOException {
BugPatternInstance pattern = new Gson().fromJson(line, BugPatternInstance.class);
pattern.severity = severityRemapper.apply(pattern);
result.add(pattern);
// replace spaces in filename with underscores
Path checkPath = Paths.get(pattern.name.replace(' ', '_') + ".md");
try (Writer writer = Files.newBufferedWriter(outputDir.resolve(checkPath), UTF_8)) {
// load side-car explanation file, if it exists
Path sidecarExplanation = explanationDir.resolve(checkPath);
if (Files.exists(sidecarExplanation)) {
if (!pattern.explanation.isEmpty()) {
throw new AssertionError(
String.format(
"%s specifies an explanation via @BugPattern and side-car", pattern.name));
}
pattern.explanation = Files.readString(sidecarExplanation).trim();
}
// Construct an appropriate page for this {@code BugPattern}. Include altNames if
// there are any, and explain the correct way to suppress.
ImmutableMap.Builder<String, Object> templateData =
ImmutableMap.<String, Object>builder()
.put(
"tags", Arrays.stream(pattern.tags).map(Style::styleTag).collect(joining("\n\n")))
.put("severity", Style.styleSeverity(pattern.severity))
.put("name", pattern.name)
.put("bugpattern", String.format("%s.java", pattern.className.replace(".", "/")))
.put("className", pattern.className)
.put("summary", pattern.summary.trim())
.put("altNames", Joiner.on(", ").join(pattern.altNames))
.put("explanation", pattern.explanation.trim());
if (pattern.sampleInput != null && pattern.sampleOutput != null) {
templateData.put("hasSamples", true);
templateData.put("sampleInput", pattern.sampleInput);
templateData.put("sampleOutput", pattern.sampleOutput);
}
if (baseUrl != null) {
templateData.put("baseUrl", baseUrl);
}
if (pattern.documentSuppression) {
String suppressionString;
if (pattern.suppressionAnnotations.length == 0) {
suppressionString = "This check may not be suppressed.";
} else {
suppressionString =
pattern.suppressionAnnotations.length == 1
? "Suppress false positives by adding the suppression annotation %s to the "
+ "enclosing element."
: "Suppress false positives by adding one of these suppression annotations to "
+ "the enclosing element: %s";
suppressionString =
String.format(
suppressionString,
Arrays.stream(pattern.suppressionAnnotations)
.map((String anno) -> standardizeAnnotation(anno, pattern.name))
.collect(joining(", ")));
}
templateData.put("suppression", suppressionString);
}
MustacheFactory mf = new DefaultMustacheFactory();
Mustache mustache = mf.compile("com/google/errorprone/resources/bugpattern.mustache");
mustache.execute(writer, templateData.buildOrThrow());
}
return true;
}
private String standardizeAnnotation(String fullAnnotationName, String patternName) {
String annotationName =
fullAnnotationName.endsWith(".class")
? fullAnnotationName.substring(0, fullAnnotationName.length() - ".class".length())
: fullAnnotationName;
if (annotationName.equals(SuppressWarnings.class.getName())) {
annotationName = SuppressWarnings.class.getSimpleName() + "(\"" + patternName + "\")";
}
return "`@" + annotationName + "`";
}
@Override
public List<BugPatternInstance> getResult() {
return result;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2015 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.io.Files.asCharSource;
import static com.google.errorprone.scanner.BuiltInCheckerSuppliers.ENABLED_ERRORS;
import static com.google.errorprone.scanner.BuiltInCheckerSuppliers.ENABLED_WARNINGS;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.StreamSupport;
/**
* Utility main which consumes the same tab-delimited text file and generates GitHub pages for the
* BugPatterns.
*/
public final class DocGenTool {
@Parameters(separators = "=")
static class Options {
@Parameter(names = "-bug_patterns", description = "Path to bugPatterns.txt", required = true)
private String bugPatterns;
@Parameter(
names = "-explanations",
description = "Path to side-car explanations",
required = true)
private String explanations;
@Parameter(names = "-docs_repository", description = "Path to docs repository", required = true)
private String docsRepository;
@Parameter(
names = "-base_url",
description = "The base url for links to bugpatterns",
arity = 1)
private String baseUrl = null;
}
public static void main(String[] args) throws IOException {
Options options = new Options();
new JCommander(options).parse(args);
Path bugPatterns = Paths.get(options.bugPatterns);
if (!Files.exists(bugPatterns)) {
usage("Cannot find bugPatterns file: " + options.bugPatterns);
}
Path explanationDir = Paths.get(options.explanations);
if (!Files.exists(explanationDir)) {
usage("Cannot find explanations dir: " + options.explanations);
}
Path wikiDir = Paths.get(options.docsRepository);
Files.createDirectories(wikiDir);
Path bugpatternDir = wikiDir.resolve("bugpatterns");
if (!Files.exists(bugpatternDir)) {
Files.createDirectories(bugpatternDir);
}
BugPatternFileGenerator generator =
new BugPatternFileGenerator(
bugpatternDir,
explanationDir,
options.baseUrl,
input -> input.severity);
try (Writer w =
Files.newBufferedWriter(wikiDir.resolve("bugpatterns.md"), StandardCharsets.UTF_8)) {
List<BugPatternInstance> patterns =
asCharSource(bugPatterns.toFile(), UTF_8).readLines(generator);
}
}
private static ImmutableSet<String> enabledCheckNames() {
return StreamSupport.stream(
Iterables.concat(ENABLED_ERRORS, ENABLED_WARNINGS).spliterator(), false)
.map(BugCheckerInfo::canonicalName)
.collect(toImmutableSet());
}
private static void usage(String err) {
System.err.println(err);
System.exit(1);
}
private DocGenTool() {}
}

View File

@@ -0,0 +1,27 @@
package com.google.errorprone;
import com.google.errorprone.BugPattern.SeverityLevel;
public final class Style {
public static String styleSeverity (SeverityLevel severityLevel) {
return String.format("%s\n {: .label .label-%s}", severityLevel.toString(), getSeverityLabelColour(severityLevel));
}
private static String getSeverityLabelColour (SeverityLevel severityLevel) {
switch (severityLevel) {
case ERROR:
return "red";
case WARNING:
return "yellow";
case SUGGESTION:
return "green";
default:
return "blue";
}
}
public static String styleTag (String tagName) {
return String.format("%s\n {: .label }", tagName);
}
}

View File

@@ -0,0 +1,50 @@
---
layout: default
title: {{name}}
description: "{{{summary}}}"
parent: Bug Patterns
nav_order: 1
---
<!--
*** AUTO-GENERATED, DO NOT MODIFY ***
To make changes, edit the @BugPattern annotation or the explanation in docs/bugpattern.
-->
# {{name}}
{{{severity}}}
{{{tags}}}
{: .summary }
{{{summary}}}
{{{explanation}}}
{{#suppression}}
## Suppression
{{{suppression}}}
{{/suppression}}
{{#hasSamples}}
## Samples
### Before
```java
{{{sampleInput}}}
```
### After
```java
{{{sampleOutput}}}
```
{{/hasSamples}}
<a href="https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/{{bugpattern}}" class="fs-3 btn external"
target="_blank">
View source code on GitHub
<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title">
<use xlink:href="#svg-external-link"></use>
</svg>
</a>

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2014 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.CharStreams;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.gson.Gson;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class BugPatternFileGeneratorTest {
@Rule public TemporaryFolder tmpfolder = new TemporaryFolder();
private Path wikiDir;
private Path explanationDirBase;
@Before
public void setUp() throws Exception {
wikiDir = tmpfolder.newFolder("wiki").toPath();
explanationDirBase = tmpfolder.newFolder("explanations").toPath();
}
private static BugPatternInstance deadExceptionTestInfo() {
BugPatternInstance instance = new BugPatternInstance();
instance.className = "com.google.errorprone.bugpatterns.DeadException";
instance.name = "DeadException";
instance.summary = "Exception created but not thrown";
instance.explanation =
"The exception is created with new, but is not thrown, and the reference is lost.";
instance.altNames = new String[] {"ThrowableInstanceNeverThrown"};
instance.tags = new String[] {"LikelyError"};
instance.severity = SeverityLevel.ERROR;
instance.suppressionAnnotations = new String[] {"java.lang.SuppressWarnings.class"};
return instance;
}
private static final String BUGPATTERN_LINE;
static {
BugPatternInstance instance = deadExceptionTestInfo();
BUGPATTERN_LINE = new Gson().toJson(instance);
}
private static final String BUGPATTERN_LINE_SIDECAR;
static {
BugPatternInstance instance = deadExceptionTestInfo();
instance.explanation = "";
BUGPATTERN_LINE_SIDECAR = new Gson().toJson(instance);
}
// Assert that the generator produces the same output it did before.
// This is brittle, but you can open the golden file
// src/test/resources/com/google/errorprone/DeadException.md
// in the same Jekyll environment you use for prod, and verify it looks good.
@Test
public void regressionTest_frontmatter_pygments() throws Exception {
BugPatternFileGenerator generator =
new BugPatternFileGenerator(
wikiDir, explanationDirBase, null, input -> input.severity);
generator.processLine(BUGPATTERN_LINE);
String expected =
CharStreams.toString(
new InputStreamReader(
getClass().getResourceAsStream("testdata/DeadException_frontmatter_pygments.md"),
UTF_8));
String actual =
CharStreams.toString(Files.newBufferedReader(wikiDir.resolve("DeadException.md"), UTF_8));
assertThat(actual.trim()).isEqualTo(expected.trim());
}
@Test
public void regressionTest_nofrontmatter_gfm() throws Exception {
BugPatternFileGenerator generator =
new BugPatternFileGenerator(
wikiDir, explanationDirBase, null, input -> input.severity);
generator.processLine(BUGPATTERN_LINE);
String expected =
CharStreams.toString(
new InputStreamReader(
getClass().getResourceAsStream("testdata/DeadException_nofrontmatter_gfm.md"),
UTF_8));
String actual = new String(Files.readAllBytes(wikiDir.resolve("DeadException.md")), UTF_8);
assertThat(actual.trim()).isEqualTo(expected.trim());
}
@Test
public void regressionTest_sidecar() throws Exception {
BugPatternFileGenerator generator =
new BugPatternFileGenerator(
wikiDir, explanationDirBase, null, input -> input.severity);
Files.write(
explanationDirBase.resolve("DeadException.md"),
Arrays.asList(
"The exception is created with new, but is not thrown, and the reference is lost."),
UTF_8);
generator.processLine(BUGPATTERN_LINE_SIDECAR);
String expected =
CharStreams.toString(
new InputStreamReader(
getClass().getResourceAsStream("testdata/DeadException_nofrontmatter_gfm.md"),
UTF_8));
String actual = new String(Files.readAllBytes(wikiDir.resolve("DeadException.md")), UTF_8);
assertThat(actual.trim()).isEqualTo(expected.trim());
}
@Test
public void testEscapeAngleBracketsInSummary() throws Exception {
// Create a BugPattern with angle brackets in the summary
BugPatternInstance instance = new BugPatternInstance();
instance.className = "com.google.errorprone.bugpatterns.DontDoThis";
instance.name = "DontDoThis";
instance.summary = "Don't do this; do List<Foo> instead";
instance.explanation = "This is a bad idea, you want `List<Foo>` instead";
instance.altNames = new String[0];
instance.tags = new String[] {"LikelyError"};
instance.severity = SeverityLevel.ERROR;
instance.suppressionAnnotations = new String[] {"java.lang.SuppressWarnings.class"};
// Write markdown file
BugPatternFileGenerator generator =
new BugPatternFileGenerator(
wikiDir, explanationDirBase, null, input -> input.severity);
generator.processLine(new Gson().toJson(instance));
String expected =
CharStreams.toString(
new InputStreamReader(
getClass().getResourceAsStream("testdata/DontDoThis_nofrontmatter_gfm.md"), UTF_8));
String actual = new String(Files.readAllBytes(wikiDir.resolve("DontDoThis.md")), UTF_8);
assertThat(actual.trim()).isEqualTo(expected.trim());
}
}

View File

@@ -0,0 +1,20 @@
---
title: DeadException
summary: Exception created but not thrown
layout: bugpattern
tags: LikelyError
severity: ERROR
---
<!--
*** AUTO-GENERATED, DO NOT MODIFY ***
To make changes, edit the @BugPattern annotation or the explanation in docs/bugpattern.
-->
_Alternate names: ThrowableInstanceNeverThrown_
## The problem
The exception is created with new, but is not thrown, and the reference is lost.
## Suppression
Suppress false positives by adding the suppression annotation `@SuppressWarnings("DeadException")` to the enclosing element.

View File

@@ -0,0 +1,21 @@
<!--
*** AUTO-GENERATED, DO NOT MODIFY ***
To make changes, edit the @BugPattern annotation or the explanation in docs/bugpattern.
-->
# DeadException
__Exception created but not thrown__
<div style="float:right;"><table id="metadata">
<tr><td>Severity</td><td>ERROR</td></tr>
<tr><td>Tags</td><td>LikelyError</td></tr>
</table></div>
_Alternate names: ThrowableInstanceNeverThrown_
## The problem
The exception is created with new, but is not thrown, and the reference is lost.
## Suppression
Suppress false positives by adding the suppression annotation `@SuppressWarnings("DeadException")` to the enclosing element.

View File

@@ -0,0 +1,20 @@
<!--
*** AUTO-GENERATED, DO NOT MODIFY ***
To make changes, edit the @BugPattern annotation or the explanation in docs/bugpattern.
-->
# DontDoThis
__Don&#39;t do this; do List&lt;Foo&gt; instead__
<div style="float:right;"><table id="metadata">
<tr><td>Severity</td><td>ERROR</td></tr>
<tr><td>Tags</td><td>LikelyError</td></tr>
</table></div>
## The problem
This is a bad idea, you want `List<Foo>` instead
## Suppression
Suppress false positives by adding the suppression annotation `@SuppressWarnings("DontDoThis")` to the enclosing element.

94
docgen_processor/pom.xml Normal file
View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2011 The Error Prone Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.2.1-SNAPSHOT</version>
</parent>
<name>Picnic :: Error Prone Support :: DocGen Processor</name>
<artifactId>error_prone_docgen_processor</artifactId>
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>${version.auto-service}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>${version.auto-service}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2015 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import com.google.common.base.Preconditions;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import org.apache.commons.text.StringEscapeUtils;
/** A serialization-friendly POJO of the information in a {@link BugPattern}. */
public final class BugPatternInstance {
private static final Formatter FORMATTER = new Formatter();
private static final Pattern INPUT_LINES_PATTERN = Pattern.compile("\\.addInputLines\\((.*?)\\)\n", Pattern.DOTALL);
private static final Pattern OUTPUT_LINES_PATTERN = Pattern.compile("\\.addOutputLines\\((.*?)\\)\n", Pattern.DOTALL);
private static final Pattern LINES_PATTERN = Pattern.compile("\"(.*)\"");
public String className;
public String name;
public String summary;
public String explanation;
public String[] altNames;
public String category;
public String[] tags;
public SeverityLevel severity;
public String[] suppressionAnnotations;
public boolean documentSuppression = true;
public String testContent;
public String sampleInput;
public String sampleOutput;
public static BugPatternInstance fromElement(Element element) {
BugPatternInstance instance = new BugPatternInstance();
instance.className = element.toString();
BugPattern annotation = element.getAnnotation(BugPattern.class);
instance.name = annotation.name().isEmpty() ? element.getSimpleName().toString() : annotation.name();
instance.altNames = annotation.altNames();
instance.tags = annotation.tags();
instance.severity = annotation.severity();
instance.summary = annotation.summary();
instance.explanation = annotation.explanation();
instance.documentSuppression = annotation.documentSuppression();
Map<String, Object> keyValues = getAnnotation(element, BugPattern.class.getName());
Object suppression = keyValues.get("suppressionAnnotations");
if (suppression == null) {
instance.suppressionAnnotations = new String[] { SuppressWarnings.class.getName() };
} else {
Preconditions.checkState(suppression instanceof List);
@SuppressWarnings("unchecked") // Always List<? extends AnnotationValue>, see above.
List<? extends AnnotationValue> resultList = (List<? extends AnnotationValue>) suppression;
instance.suppressionAnnotations = resultList.stream().map(AnnotationValue::toString).toArray(String[]::new);
}
Path testPath = getPath(instance);
System.out.println("test class for " + instance.name + " = " + testPath.toAbsolutePath());
try {
instance.testContent = String.join("\n", Files.readAllLines(testPath));
instance.sampleInput = getInputLines(instance.testContent);
instance.sampleOutput = getOutputLines(instance.testContent);
} catch (IOException e) {
e.printStackTrace();
}
return instance;
}
private static Path getPath(BugPatternInstance instance) {
return Path.of(
"error-prone-contrib/src/test/java/"
+ instance.className.replace(".", "/")
+ "Test.java");
}
private static String getInputLines(String content) {
return getLines(INPUT_LINES_PATTERN, content);
}
private static String getOutputLines(String content) {
return getLines(OUTPUT_LINES_PATTERN, content);
}
private static String getLines(Pattern pattern, String content) {
Matcher match = pattern.matcher(content);
if (!match.find()) {
return null;
}
String argument = match.group(1);
List<String> lines = findAllGroups(argument, LINES_PATTERN);
// Remove in/A.java and out/A.java
lines.remove(0);
String sampleCode = String.join("\n", lines);
sampleCode = StringEscapeUtils.unescapeJava(sampleCode);
try {
// Trim to remove trailing line-break.
return FORMATTER.formatSource(sampleCode).trim();
} catch (FormatterException e) {
e.printStackTrace();
return null;
}
}
private static List<String> findAllGroups(String text, Pattern pattern) {
List<String> list = new ArrayList<>();
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
for (int i = 1; i <= matcher.groupCount(); i++) {
list.add(matcher.group(i));
}
}
return list;
}
private static Map<String, Object> getAnnotation(Element element, String name) {
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
if (mirror.getAnnotationType().toString().equals(name)) {
return annotationKeyValues(mirror);
}
}
throw new IllegalArgumentException(String.format("%s has no annotation %s", element, name));
}
private static Map<String, Object> annotationKeyValues(AnnotationMirror mirror) {
Map<String, Object> result = new LinkedHashMap<>();
for (ExecutableElement key : mirror.getElementValues().keySet()) {
result.put(key.getSimpleName().toString(), mirror.getElementValues().get(key).getValue());
}
return result;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2011 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.auto.service.AutoService;
import com.google.googlejavaformat.java.Formatter;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
/**
* Annotation processor which visits all classes that have a {@code BugPattern} annotation, and
* writes a tab-delimited text file dumping the data found.
*
* @author eaftan@google.com (Eddie Aftandilian)
* @author alexeagle@google.com (Alex Eagle)
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.google.errorprone.BugPattern")
public class DocGenProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
private final Gson gson = new Gson();
private PrintWriter pw;
/** {@inheritDoc} */
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
try {
FileObject manifest =
processingEnv
.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, "", "bugPatterns.txt");
pw = new PrintWriter(new OutputStreamWriter(manifest.openOutputStream(), UTF_8), true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** {@inheritDoc} */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(BugPattern.class)) {
System.out.println("[DOCGEN] HANDLING: " + element.getSimpleName());
gson.toJson(BugPatternInstance.fromElement(element), pw);
pw.println();
}
if (roundEnv.processingOver()) {
// this was the last round, do cleanup
cleanup();
}
return false;
}
/** Perform cleanup after last round of annotation processing. */
private void cleanup() {
pw.close();
}
}

View File

@@ -0,0 +1,90 @@
package tech.picnic.errorprone.docgen;
import static java.util.stream.Collectors.joining;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
class ExampleExtractorTest {
private static final String INPUT =
String.join("\n",
"@Test",
"void replacementFirstSuggestedFix() {",
" refactoringTestHelper",
" .addInputLines(",
" \"A.java\",",
" \"import static java.util.stream.Collectors.toList;\",",
" \"import static java.util.stream.Collectors.toMap;\",",
" \"import static java.util.stream.Collectors.toSet;\",",
" \"\",",
" \"import java.util.stream.Collectors;\",",
" \"import java.util.stream.Stream;\",",
" \"import reactor.core.publisher.Flux;\",",
" \"\",",
" \"class A {\",",
" \" void m() {\",",
" \" Flux.just(1).collect(Collectors.toList());\",",
" \" Flux.just(2).collect(toList());\",",
" \"\",",
" \" Stream.of(\"foo\").collect(Collectors.toMap(String::getBytes, String::length));\",",
" \" Stream.of(\"bar\").collect(toMap(String::getBytes, String::length));\",",
" \" Flux.just(\"baz\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b));\",",
" \" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b));\",",
" \"\",",
" \" Stream.of(1).collect(Collectors.toSet());\",",
" \" Stream.of(2).collect(toSet());\",",
" \" }\",",
" \"}\")",
" .addOutputLines(",
" \"A.java\",",
" \"import static com.google.common.collect.ImmutableList.toImmutableList;\",",
" \"import static com.google.common.collect.ImmutableMap.toImmutableMap;\",",
" \"import static com.google.common.collect.ImmutableSet.toImmutableSet;\",",
" \"import static java.util.stream.Collectors.toList;\",",
" \"import static java.util.stream.Collectors.toMap;\",",
" \"import static java.util.stream.Collectors.toSet;\",",
" \"\",",
" \"import java.util.stream.Collectors;\",",
" \"import java.util.stream.Stream;\",",
" \"import reactor.core.publisher.Flux;\",",
" \"\",",
" \"class A {\",",
" \" void m() {\",",
" \" Flux.just(1).collect(toImmutableList());\",",
" \" Flux.just(2).collect(toImmutableList());\",",
" \"\",",
" \" Stream.of(\"foo\").collect(toImmutableMap(String::getBytes, String::length));\",",
" \" Stream.of(\"bar\").collect(toImmutableMap(String::getBytes, String::length));\",",
" \" Flux.just(\"baz\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));\",",
" \" Flux.just(\"qux\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));\",",
" \"\",",
" \" Stream.of(1).collect(toImmutableSet());\",",
" \" Stream.of(2).collect(toImmutableSet());\",",
" \" }\",",
" \"}\")",
" .doTest(TestMode.TEXT_MATCH);",
"}");
@Test
void regexTest() throws FormatterException {
final Formatter FORMATTER = new Formatter();
Pattern pattern =
Pattern.compile("\\.addInputLines\\((\n.*?\".*?\",)\n(.*?)\\)\n", Pattern.DOTALL);
Matcher matcher = pattern.matcher(INPUT);
int count = matcher.groupCount();
if(!matcher.find()) {
System.out.println("no match!");
return;
}
String src = matcher.group(2);
System.out.println("\\\"foo\\\"".replaceAll("\\\\\"(.*?)\\\\\"", "\"$1\""));
}
}

View File

@@ -0,0 +1 @@
There's not much use to keep empty methods.

View File

@@ -0,0 +1,12 @@
## Problem
The results of the `BigDecimal` constructor can be somewhat unpredictable. One
might assume that writing `new BigDecimal(0.1)` in Java creates a `BigDecimal`
which is exactly equal to `0.1` (an unscaled value of `1`, with a scale of
`1`), but it is actually equal to
`0.1000000000000000055511151231257827021181583404541015625`.
This is because
`0.1` cannot be represented exactly as a `double` (or, for that matter, as a
binary fraction of any finite length). Thus, the value that is being passed in
to the constructor is not exactly equal to `0.1`, appearances notwithstanding.

View File

@@ -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:
@@ -118,12 +69,13 @@ The following is a list of checks we'd like to see implemented:
code and Javadoc `@link` references.
- A check which 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
%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.)
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 %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.
For example as argument to `StringBuilder.append` and in string
concatenations.
@@ -168,11 +120,11 @@ The following is a list of checks we'd like to see implemented:
- A check which flags imports from other test classes.
- A Guava-specific check which 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
IllegalArgumentException(); }` and variants to an equivalent `checkArgument`
statement. Idem for other exception types.
- 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 IllegalArgumentException(); }` and variants to
an equivalent `checkArgument` statement. Idem for other exception types.
- A Guava-specific check which replaces simple anonymous `CacheLoader` subclass
declarations with `CacheLoader.from(someLambda)`.
- A Spring-specific check which enforces that methods with the `@Scheduled`
@@ -247,6 +199,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.
@@ -259,16 +212,16 @@ Refaster's expressiveness:
- Some Refaster refactorings (e.g. when dealing with lazy evaluation) are valid
only when some free parameter is a constant, variable reference or some other
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.
rewriting `optional1.map(Optional::of).orElse(optional2)` to
`optional1.or(() -> optional2)` is not behavior preserving if evaluation of
`optional2` has 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
this.
- Generalize `@Placeholder` support such that rules can reference e.g. "any
concrete unary method". This would allow refactorings such as
`Mono.just(constant).flatmap(this::someFun)` -> `Mono.defer(() ->
someFun(constant))`.
`Mono.just(constant).flatmap(this::someFun)` ->
`Mono.defer(() -> someFun(constant))`.
- Sometimes a Refaster refactoring can cause the resulting code not to compile
due to a lack of generic type information. Identify and resolve such
occurrences. For example, an `@AfterTemplate` may require the insertion of a
@@ -327,16 +280,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

View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -69,11 +70,6 @@
<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>
@@ -128,11 +124,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>
@@ -148,6 +139,11 @@
<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>
@@ -236,11 +232,57 @@
<ignoredUnusedDeclaredDependencies>
<!-- XXX: Figure out why the plugin thinks this
dependency is unused. -->
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support</ignoredUnusedDeclaredDependency>
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support
</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<!-- run annotation processor -->
<profile>
<id>run-annotation-processor</id>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>error_prone_docgen_processor</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${version.auto-value}</version>
</path>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>${version.auto-service}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>error_prone_docgen_processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,10 +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.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;
@@ -13,7 +16,9 @@ import static com.google.errorprone.matchers.method.MethodMatchers.instanceMetho
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
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 +29,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,6 +47,7 @@ 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;
@@ -69,49 +76,30 @@ public final class RedundantStringConversion 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())
@@ -127,17 +115,10 @@ public final class RedundantStringConversion 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()
@@ -156,7 +137,7 @@ public final class RedundantStringConversion 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 RedundantStringConversion}. */
public RedundantStringConversion() {
@@ -186,7 +167,7 @@ public final class RedundantStringConversion 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);
@@ -276,15 +257,15 @@ public final class RedundantStringConversion extends BugChecker
// XXX: Write another check which 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.
*/
@@ -338,11 +319,15 @@ public final class RedundantStringConversion 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);
@@ -383,7 +368,8 @@ public final class RedundantStringConversion 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

View File

@@ -66,7 +66,8 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
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"),

View File

@@ -0,0 +1,92 @@
package tech.picnic.errorprone.refastertemplates;
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;
final class AssertJComparableTemplates {
private AssertJComparableTemplates() {}
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);
}
}
}

View File

@@ -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,6 +19,7 @@ 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.util.IsCharacter;
final class AssertJNumberTemplates {
private AssertJNumberTemplates() {}
@@ -226,9 +228,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 +253,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);
}

View File

@@ -0,0 +1,111 @@
package tech.picnic.errorprone.refastertemplates;
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;
final class AssertJPrimitiveTemplates {
private AssertJPrimitiveTemplates() {}
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);
}
}
}

View File

@@ -41,13 +41,11 @@ final class EqualityTemplates {
// 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;
}
@@ -70,17 +68,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);
@@ -101,17 +96,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);

View File

@@ -9,11 +9,38 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.function.Predicate;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with (possibly) null values. */
final class NullTemplates {
private NullTemplates() {}
/** 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
// will return `null`, while the JDK variant will throw an NPE.
@@ -33,13 +60,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 +73,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;
}

View File

@@ -9,6 +9,7 @@ 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;
@@ -81,13 +82,11 @@ final class OptionalTemplates {
// generalization. If/when Refaster is extended to understand this, delete the template 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;
}
@@ -334,6 +333,26 @@ 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;
}
}
// 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.)

View File

@@ -10,11 +10,6 @@ final class PrimitiveTemplates {
/** 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 +23,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 +36,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 +49,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);

View File

@@ -9,10 +9,12 @@ 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.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -22,16 +24,35 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.publisher.PublisherProbe;
import tech.picnic.errorprone.refaster.util.ThrowsCheckedException;
/** Refaster templates related to Reactor expressions and statements. */
final class ReactorTemplates {
private ReactorTemplates() {}
/**
* Prefer {@link Mono#fromSupplier(Supplier)} over {@link Mono#fromCallable(Callable)} where
* feasible.
*/
static final class MonoFromSupplier<T> {
@BeforeTemplate
Mono<T> before(@NotMatches(ThrowsCheckedException.class) Callable<? extends T> supplier) {
return Mono.fromCallable(supplier);
}
@AfterTemplate
Mono<T> after(Supplier<? extends T> supplier) {
return Mono.fromSupplier(supplier);
}
}
/** Prefer {@link Mono#justOrEmpty(Optional)} over more verbose alternatives. */
// XXX: If `optional` is a constant and effectively-final expression then the `Mono.defer` can be
// dropped. Should look into Refaster support for identifying this.
static final class MonoFromOptional<T> {
@BeforeTemplate
@SuppressWarnings(
"MonoFromSupplier" /* `optional` may match a checked exception-throwing expression. */)
Mono<T> before(Optional<T> optional) {
return Refaster.anyOf(
Mono.fromCallable(() -> optional.orElse(null)),

View File

@@ -9,7 +9,7 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import org.reactivestreams.Publisher;
import org.jspecify.nullness.Nullable;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -21,14 +21,14 @@ final class RxJava2AdapterTemplates {
/** Use the fluent API style when using {@link RxJava2Adapter#completableToMono}. */
static final class CompletableToMono {
@BeforeTemplate
Mono<Void> before(Completable completable) {
Mono<@Nullable Void> before(Completable completable) {
return Refaster.anyOf(
RxJava2Adapter.completableToMono(completable),
completable.to(RxJava2Adapter::completableToMono));
}
@AfterTemplate
Mono<Void> after(Completable completable) {
Mono<@Nullable Void> after(Completable completable) {
return completable.as(RxJava2Adapter::completableToMono);
}
}
@@ -39,12 +39,12 @@ final class RxJava2AdapterTemplates {
*/
static final class FlowableToFlux<T> {
@BeforeTemplate
Publisher<T> before(Flowable<T> flowable) {
Flux<T> before(Flowable<T> flowable) {
return Refaster.anyOf(
flowable.compose(Flux::from),
Flux.from(flowable),
flowable.to(Flux::from),
flowable.as(Flux::from),
flowable.compose(RxJava2Adapter::flowableToFlux),
RxJava2Adapter.flowableToFlux(flowable),
flowable.to(RxJava2Adapter::flowableToFlux));
}
@@ -60,12 +60,11 @@ final class RxJava2AdapterTemplates {
*/
static final class FluxToFlowable<T> {
@BeforeTemplate
Publisher<T> before(Flux<T> flux) {
Flowable<T> before(Flux<T> flux) {
return Refaster.anyOf(
Flowable.fromPublisher(flux),
flux.transform(Flowable::fromPublisher),
flux.as(Flowable::fromPublisher),
flux.transform(RxJava2Adapter::fluxToFlowable));
RxJava2Adapter.fluxToFlowable(flux));
}
@AfterTemplate
@@ -132,12 +131,11 @@ final class RxJava2AdapterTemplates {
*/
static final class MonoToFlowable<T> {
@BeforeTemplate
Publisher<T> before(Mono<T> mono) {
Flowable<T> before(Mono<T> mono) {
return Refaster.anyOf(
Flowable.fromPublisher(mono),
mono.transform(Flowable::fromPublisher),
mono.as(Flowable::fromPublisher),
mono.transform(RxJava2Adapter::monoToFlowable));
RxJava2Adapter.monoToFlowable(mono));
}
@AfterTemplate

View File

@@ -13,7 +13,9 @@ import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with {@link String}s. */
@@ -106,6 +108,40 @@ final class StringTemplates {
}
}
/**
* Prefer direct invocation of {@link String#valueOf(Object)} over the indirection introduced by
* {@link Objects#toString(Object)}.
*/
static final class StringValueOf {
@BeforeTemplate
String before(Object object) {
return Objects.toString(object);
}
@AfterTemplate
String after(Object object) {
return String.valueOf(object);
}
}
/**
* Prefer direct delegation to {@link String#valueOf(Object)} over the indirection introduced by
* {@link Objects#toString(Object)}.
*/
// XXX: This template is analogous to `StringValueOf` above. Arguably this is its generalization.
// If/when Refaster is extended to understand this, delete the template above.
static final class StringValueOfMethodReference {
@BeforeTemplate
Function<Object, String> before() {
return Objects::toString;
}
@AfterTemplate
Function<Object, String> after() {
return String::valueOf;
}
}
/** Don't unnecessarily use the two-argument {@link String#substring(int, int)}. */
static final class SubstringRemainder {
@BeforeTemplate

View File

@@ -69,9 +69,14 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s += String.valueOf(i);",
" // BUG: Diagnostic contains:",
" s += String.valueOf(0);",
" // BUG: Diagnostic contains:",
" s += String.valueOf((String) null);",
" s += String.valueOf(null);",
" s += String.valueOf(new char[0]);",
" s += String.valueOf(new char[0], 0, 0);",
" // BUG: Diagnostic contains:",
" s += Boolean.toString(false);",
" // BUG: Diagnostic contains:",
" s += Byte.toString((byte) 0);",
" // BUG: Diagnostic contains:",
@@ -121,9 +126,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s + String.valueOf(i),",
" // BUG: Diagnostic contains:",
" s + String.valueOf(0),",
" // BUG: Diagnostic contains:",
" s + String.valueOf((String) null),",
" s + String.valueOf(null),",
" s + String.valueOf(new char[0]),",
" s + String.valueOf(new char[0], 0, 0),",
" //",
" 42 + this.toString(),",
" 42 + super.toString(),",
@@ -134,6 +142,7 @@ final class RedundantStringConversionTest {
" 42 + String.valueOf((String) null),",
" 42 + String.valueOf(null),",
" 42 + String.valueOf(new char[0]),",
" 42 + String.valueOf(new char[0], 0, 0),",
"",
" // BUG: Diagnostic contains:",
" this.toString() + s,",
@@ -144,19 +153,24 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" String.valueOf(i) + s,",
" // BUG: Diagnostic contains:",
" String.valueOf(0) + s,",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + s,",
" String.valueOf(null) + s,",
" String.valueOf(new char[0]) + s,",
" String.valueOf(new char[0], 0, 0) + s,",
" //",
" this.toString() + 42,",
" super.toString() + 42,",
" i.toString() + 42,",
" i.toString(16) + 42,",
" String.valueOf(i) + 42,",
" String.valueOf(0) + 42,",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + 42,",
" String.valueOf(null) + 42,",
" String.valueOf(new char[0]) + 42,",
" String.valueOf(new char[0], 0, 0) + 42,",
"",
" // BUG: Diagnostic contains:",
" this.toString() + this.toString(),",
@@ -167,9 +181,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" String.valueOf(i) + String.valueOf(i),",
" // BUG: Diagnostic contains:",
" String.valueOf(0) + String.valueOf(0),",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + String.valueOf((String) null),",
" String.valueOf(null) + String.valueOf(null),",
" String.valueOf(new char[0]) + String.valueOf(new char[0]),",
" String.valueOf(new char[0], 0, 0) + String.valueOf(new char[0], 0, 0),",
" };",
" }",
"",
@@ -204,9 +221,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf(i));",
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf(0));",
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf((String) null));",
" sb.append(String.valueOf(null));",
" sb.append(String.valueOf(new char[0]));",
" sb.append(String.valueOf(new char[0], 0, 0));",
" sb.append(s);",
" sb.append(\"constant\");",
"",
@@ -218,9 +238,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf(i));",
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf(0));",
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf((String) null));",
" sb.insert(0, String.valueOf(null));",
" sb.insert(0, String.valueOf(new char[0]));",
" sb.insert(0, String.valueOf(new char[0], 0, 0));",
" sb.insert(0, s);",
" sb.insert(0, \"constant\");",
"",
@@ -434,6 +457,8 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s + String.valueOf(b),",
" // BUG: Diagnostic contains:",
" s + Boolean.toString(false),",
" // BUG: Diagnostic contains:",
" s + Byte.toString((byte) 0),",
" // BUG: Diagnostic contains:",
" s + Character.toString((char) 0),",

View File

@@ -30,6 +30,7 @@ final class RequestMappingAnnotationTest {
"import org.springframework.web.bind.annotation.RequestHeader;",
"import org.springframework.web.bind.annotation.RequestMapping;",
"import org.springframework.web.bind.annotation.RequestParam;",
"import org.springframework.web.bind.annotation.RequestPart;",
"import org.springframework.web.context.request.NativeWebRequest;",
"import org.springframework.web.context.request.WebRequest;",
"import org.springframework.web.server.ServerWebExchange;",
@@ -60,6 +61,9 @@ final class RequestMappingAnnotationTest {
" A properRequestParam(@RequestParam String param);",
"",
" @RequestMapping",
" A properRequestPart(@RequestPart String part);",
"",
" @RequestMapping",
" A properInputStream(InputStream input);",
"",
" @RequestMapping",

View File

@@ -19,6 +19,7 @@ final class RefasterTemplatesTest {
AssertJBooleanTemplates.class,
AssertJByteTemplates.class,
AssertJCharSequenceTemplates.class,
AssertJComparableTemplates.class,
AssertJDoubleTemplates.class,
AssertJEnumerableTemplates.class,
AssertJFloatTemplates.class,
@@ -28,6 +29,7 @@ final class RefasterTemplatesTest {
AssertJMapTemplates.class,
AssertJObjectTemplates.class,
AssertJOptionalTemplates.class,
AssertJPrimitiveTemplates.class,
AssertJShortTemplates.class,
AssertJStringTemplates.class,
AssertJThrowingCallableTemplates.class,

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.refastertemplates;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractComparableAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJComparableTemplatesTest implements RefasterTemplateTestCase {
AbstractComparableAssert<?, ?> testAssertThatIsEqualByComparingTo() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isEqualTo(0);
}
AbstractComparableAssert<?, ?> testAssertThatIsNotEqualByComparingTo() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isNotEqualTo(0);
}
AbstractComparableAssert<?, ?> testAssertThatIsLessThan() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isNegative();
}
AbstractComparableAssert<?, ?> testAssertThatIsLessThanOrEqualTo() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isNotPositive();
}
AbstractComparableAssert<?, ?> testAssertThatIsGreaterThan() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isPositive();
}
AbstractComparableAssert<?, ?> testAssertThatIsGreaterThanOrEqualTo() {
return assertThat(BigDecimal.ZERO.compareTo(BigDecimal.ONE)).isNotNegative();
}
}

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.refastertemplates;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractComparableAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJComparableTemplatesTest implements RefasterTemplateTestCase {
AbstractComparableAssert<?, ?> testAssertThatIsEqualByComparingTo() {
return assertThat(BigDecimal.ZERO).isEqualByComparingTo(BigDecimal.ONE);
}
AbstractComparableAssert<?, ?> testAssertThatIsNotEqualByComparingTo() {
return assertThat(BigDecimal.ZERO).isNotEqualByComparingTo(BigDecimal.ONE);
}
AbstractComparableAssert<?, ?> testAssertThatIsLessThan() {
return assertThat(BigDecimal.ZERO).isLessThan(BigDecimal.ONE);
}
AbstractComparableAssert<?, ?> testAssertThatIsLessThanOrEqualTo() {
return assertThat(BigDecimal.ZERO).isLessThanOrEqualTo(BigDecimal.ONE);
}
AbstractComparableAssert<?, ?> testAssertThatIsGreaterThan() {
return assertThat(BigDecimal.ZERO).isGreaterThan(BigDecimal.ONE);
}
AbstractComparableAssert<?, ?> testAssertThatIsGreaterThanOrEqualTo() {
return assertThat(BigDecimal.ZERO).isGreaterThanOrEqualTo(BigDecimal.ONE);
}
}

View File

@@ -81,23 +81,27 @@ final class AssertJNumberTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(
assertThat((byte) 1 % 2).isEqualTo(1),
assertThat(Byte.valueOf((byte) 1) % 2).isEqualTo(1),
assertThat((char) 1 % 2).isEqualTo(1),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(1),
assertThat((short) 1 % 2).isEqualTo(1),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(1),
assertThat(1 % 2).isEqualTo(1),
assertThat(Integer.valueOf(1) % 2).isEqualTo(1),
assertThat(1L % 2).isEqualTo(1),
assertThat(Long.valueOf(1) % 2).isEqualTo(1),
assertThat((short) 1 % 2).isEqualTo(1),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(1));
assertThat(Long.valueOf(1) % 2).isEqualTo(1));
}
ImmutableSet<NumberAssert<?, ?>> testAssertThatIsEven() {
return ImmutableSet.of(
assertThat((byte) 1 % 2).isEqualTo(0),
assertThat(Byte.valueOf((byte) 1) % 2).isEqualTo(0),
assertThat((char) 1 % 2).isEqualTo(0),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(0),
assertThat((short) 1 % 2).isEqualTo(0),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(0),
assertThat(1 % 2).isEqualTo(0),
assertThat(Integer.valueOf(1) % 2).isEqualTo(0),
assertThat(1L % 2).isEqualTo(0),
assertThat(Long.valueOf(1) % 2).isEqualTo(0),
assertThat((short) 1 % 2).isEqualTo(0),
assertThat(Short.valueOf((short) 1) % 2).isEqualTo(0));
assertThat(Long.valueOf(1) % 2).isEqualTo(0));
}
}

View File

@@ -81,23 +81,27 @@ final class AssertJNumberTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(
assertThat((byte) 1).isOdd(),
assertThat(Byte.valueOf((byte) 1)).isOdd(),
assertThat((char) 1 % 2).isEqualTo(1),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(1),
assertThat((short) 1).isOdd(),
assertThat(Short.valueOf((short) 1)).isOdd(),
assertThat(1).isOdd(),
assertThat(Integer.valueOf(1)).isOdd(),
assertThat(1L).isOdd(),
assertThat(Long.valueOf(1)).isOdd(),
assertThat((short) 1).isOdd(),
assertThat(Short.valueOf((short) 1)).isOdd());
assertThat(Long.valueOf(1)).isOdd());
}
ImmutableSet<NumberAssert<?, ?>> testAssertThatIsEven() {
return ImmutableSet.of(
assertThat((byte) 1).isEven(),
assertThat(Byte.valueOf((byte) 1)).isEven(),
assertThat((char) 1 % 2).isEqualTo(0),
assertThat(Character.valueOf((char) 1) % 2).isEqualTo(0),
assertThat((short) 1).isEven(),
assertThat(Short.valueOf((short) 1)).isEven(),
assertThat(1).isEven(),
assertThat(Integer.valueOf(1)).isEven(),
assertThat(1L).isEven(),
assertThat(Long.valueOf(1)).isEven(),
assertThat((short) 1).isEven(),
assertThat(Short.valueOf((short) 1)).isEven());
assertThat(Long.valueOf(1)).isEven());
}
}

View File

@@ -0,0 +1,123 @@
package tech.picnic.errorprone.refastertemplates;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJPrimitiveTemplatesTest implements RefasterTemplateTestCase {
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsEqualTo() {
return ImmutableSet.of(
assertThat(true == false).isTrue(),
assertThat(true != false).isFalse(),
assertThat((byte) 1 == (byte) 2).isTrue(),
assertThat((byte) 1 != (byte) 2).isFalse(),
assertThat((char) 1 == (char) 2).isTrue(),
assertThat((char) 1 != (char) 2).isFalse(),
assertThat((short) 1 == (short) 2).isTrue(),
assertThat((short) 1 != (short) 2).isFalse(),
assertThat(1 == 2).isTrue(),
assertThat(1 != 2).isFalse(),
assertThat(1L == 2L).isTrue(),
assertThat(1L != 2L).isFalse(),
assertThat(1F == 2F).isTrue(),
assertThat(1F != 2F).isFalse(),
assertThat(1.0 == 2.0).isTrue(),
assertThat(1.0 != 2.0).isFalse());
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsNotEqualTo() {
return ImmutableSet.of(
assertThat(true != false).isTrue(),
assertThat(true == false).isFalse(),
assertThat((byte) 1 != (byte) 2).isTrue(),
assertThat((byte) 1 == (byte) 2).isFalse(),
assertThat((char) 1 != (char) 2).isTrue(),
assertThat((char) 1 == (char) 2).isFalse(),
assertThat((short) 1 != (short) 2).isTrue(),
assertThat((short) 1 == (short) 2).isFalse(),
assertThat(1 != 2).isTrue(),
assertThat(1 == 2).isFalse(),
assertThat(1L != 2L).isTrue(),
assertThat(1L == 2L).isFalse(),
assertThat(1F != 2F).isTrue(),
assertThat(1F == 2F).isFalse(),
assertThat(1.0 != 2.0).isTrue(),
assertThat(1.0 == 2.0).isFalse());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsLessThan() {
return ImmutableSet.of(
assertThat((byte) 1 < (byte) 2).isTrue(),
assertThat((byte) 1 >= (byte) 2).isFalse(),
assertThat((char) 1 < (char) 2).isTrue(),
assertThat((char) 1 >= (char) 2).isFalse(),
assertThat((short) 1 < (short) 2).isTrue(),
assertThat((short) 1 >= (short) 2).isFalse(),
assertThat(1 < 2).isTrue(),
assertThat(1 >= 2).isFalse(),
assertThat(1L < 2L).isTrue(),
assertThat(1L >= 2L).isFalse(),
assertThat(1F < 2F).isTrue(),
assertThat(1F >= 2F).isFalse(),
assertThat(1.0 < 2.0).isTrue(),
assertThat(1.0 >= 2.0).isFalse());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsLessThanOrEqualTo() {
return ImmutableSet.of(
assertThat((byte) 1 <= (byte) 2).isTrue(),
assertThat((byte) 1 > (byte) 2).isFalse(),
assertThat((char) 1 <= (char) 2).isTrue(),
assertThat((char) 1 > (char) 2).isFalse(),
assertThat((short) 1 <= (short) 2).isTrue(),
assertThat((short) 1 > (short) 2).isFalse(),
assertThat(1 <= 2).isTrue(),
assertThat(1 > 2).isFalse(),
assertThat(1L <= 2L).isTrue(),
assertThat(1L > 2L).isFalse(),
assertThat(1F <= 2F).isTrue(),
assertThat(1F > 2F).isFalse(),
assertThat(1.0 <= 2.0).isTrue(),
assertThat(1.0 > 2.0).isFalse());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsGreaterThan() {
return ImmutableSet.of(
assertThat((byte) 1 > (byte) 2).isTrue(),
assertThat((byte) 1 <= (byte) 2).isFalse(),
assertThat((char) 1 > (char) 2).isTrue(),
assertThat((char) 1 <= (char) 2).isFalse(),
assertThat((short) 1 > (short) 2).isTrue(),
assertThat((short) 1 <= (short) 2).isFalse(),
assertThat(1 > 2).isTrue(),
assertThat(1 <= 2).isFalse(),
assertThat(1L > 2L).isTrue(),
assertThat(1L <= 2L).isFalse(),
assertThat(1F > 2F).isTrue(),
assertThat(1F <= 2F).isFalse(),
assertThat(1.0 > 2.0).isTrue(),
assertThat(1.0 <= 2.0).isFalse());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsGreaterThanOrEqualTo() {
return ImmutableSet.of(
assertThat((byte) 1 >= (byte) 2).isTrue(),
assertThat((byte) 1 < (byte) 2).isFalse(),
assertThat((char) 1 >= (char) 2).isTrue(),
assertThat((char) 1 < (char) 2).isFalse(),
assertThat((short) 1 >= (short) 2).isTrue(),
assertThat((short) 1 < (short) 2).isFalse(),
assertThat(1 >= 2).isTrue(),
assertThat(1 < 2).isFalse(),
assertThat(1L >= 2L).isTrue(),
assertThat(1L < 2L).isFalse(),
assertThat(1F >= 2F).isTrue(),
assertThat(1F < 2F).isFalse(),
assertThat(1.0 >= 2.0).isTrue(),
assertThat(1.0 < 2.0).isFalse());
}
}

View File

@@ -0,0 +1,123 @@
package tech.picnic.errorprone.refastertemplates;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJPrimitiveTemplatesTest implements RefasterTemplateTestCase {
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsEqualTo() {
return ImmutableSet.of(
assertThat(true).isEqualTo(false),
assertThat(true).isEqualTo(false),
assertThat((byte) 1).isEqualTo((byte) 2),
assertThat((byte) 1).isEqualTo((byte) 2),
assertThat((char) 1).isEqualTo((char) 2),
assertThat((char) 1).isEqualTo((char) 2),
assertThat((short) 1).isEqualTo((short) 2),
assertThat((short) 1).isEqualTo((short) 2),
assertThat(1).isEqualTo(2),
assertThat(1).isEqualTo(2),
assertThat(1L).isEqualTo(2L),
assertThat(1L).isEqualTo(2L),
assertThat(1F).isEqualTo(2F),
assertThat(1F).isEqualTo(2F),
assertThat(1.0).isEqualTo(2.0),
assertThat(1.0).isEqualTo(2.0));
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsNotEqualTo() {
return ImmutableSet.of(
assertThat(true).isNotEqualTo(false),
assertThat(true).isNotEqualTo(false),
assertThat((byte) 1).isNotEqualTo((byte) 2),
assertThat((byte) 1).isNotEqualTo((byte) 2),
assertThat((char) 1).isNotEqualTo((char) 2),
assertThat((char) 1).isNotEqualTo((char) 2),
assertThat((short) 1).isNotEqualTo((short) 2),
assertThat((short) 1).isNotEqualTo((short) 2),
assertThat(1).isNotEqualTo(2),
assertThat(1).isNotEqualTo(2),
assertThat(1L).isNotEqualTo(2L),
assertThat(1L).isNotEqualTo(2L),
assertThat(1F).isNotEqualTo(2F),
assertThat(1F).isNotEqualTo(2F),
assertThat(1.0).isNotEqualTo(2.0),
assertThat(1.0).isNotEqualTo(2.0));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsLessThan() {
return ImmutableSet.of(
assertThat((byte) 1).isLessThan((byte) 2),
assertThat((byte) 1).isLessThan((byte) 2),
assertThat((char) 1).isLessThan((char) 2),
assertThat((char) 1).isLessThan((char) 2),
assertThat((short) 1).isLessThan((short) 2),
assertThat((short) 1).isLessThan((short) 2),
assertThat(1).isLessThan(2),
assertThat(1).isLessThan(2),
assertThat(1L).isLessThan(2L),
assertThat(1L).isLessThan(2L),
assertThat(1F).isLessThan(2F),
assertThat(1F).isLessThan(2F),
assertThat(1.0).isLessThan(2.0),
assertThat(1.0).isLessThan(2.0));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsLessThanOrEqualTo() {
return ImmutableSet.of(
assertThat((byte) 1).isLessThanOrEqualTo((byte) 2),
assertThat((byte) 1).isLessThanOrEqualTo((byte) 2),
assertThat((char) 1).isLessThanOrEqualTo((char) 2),
assertThat((char) 1).isLessThanOrEqualTo((char) 2),
assertThat((short) 1).isLessThanOrEqualTo((short) 2),
assertThat((short) 1).isLessThanOrEqualTo((short) 2),
assertThat(1).isLessThanOrEqualTo(2),
assertThat(1).isLessThanOrEqualTo(2),
assertThat(1L).isLessThanOrEqualTo(2L),
assertThat(1L).isLessThanOrEqualTo(2L),
assertThat(1F).isLessThanOrEqualTo(2F),
assertThat(1F).isLessThanOrEqualTo(2F),
assertThat(1.0).isLessThanOrEqualTo(2.0),
assertThat(1.0).isLessThanOrEqualTo(2.0));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsGreaterThan() {
return ImmutableSet.of(
assertThat((byte) 1).isGreaterThan((byte) 2),
assertThat((byte) 1).isGreaterThan((byte) 2),
assertThat((char) 1).isGreaterThan((char) 2),
assertThat((char) 1).isGreaterThan((char) 2),
assertThat((short) 1).isGreaterThan((short) 2),
assertThat((short) 1).isGreaterThan((short) 2),
assertThat(1).isGreaterThan(2),
assertThat(1).isGreaterThan(2),
assertThat(1L).isGreaterThan(2L),
assertThat(1L).isGreaterThan(2L),
assertThat(1F).isGreaterThan(2F),
assertThat(1F).isGreaterThan(2F),
assertThat(1.0).isGreaterThan(2.0),
assertThat(1.0).isGreaterThan(2.0));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatIsGreaterThanOrEqualTo() {
return ImmutableSet.of(
assertThat((byte) 1).isGreaterThanOrEqualTo((byte) 2),
assertThat((byte) 1).isGreaterThanOrEqualTo((byte) 2),
assertThat((char) 1).isGreaterThanOrEqualTo((char) 2),
assertThat((char) 1).isGreaterThanOrEqualTo((char) 2),
assertThat((short) 1).isGreaterThanOrEqualTo((short) 2),
assertThat((short) 1).isGreaterThanOrEqualTo((short) 2),
assertThat(1).isGreaterThanOrEqualTo(2),
assertThat(1).isGreaterThanOrEqualTo(2),
assertThat(1L).isGreaterThanOrEqualTo(2L),
assertThat(1L).isGreaterThanOrEqualTo(2L),
assertThat(1F).isGreaterThanOrEqualTo(2F),
assertThat(1F).isGreaterThanOrEqualTo(2F),
assertThat(1.0).isGreaterThanOrEqualTo(2.0),
assertThat(1.0).isGreaterThanOrEqualTo(2.0));
}
}

View File

@@ -31,11 +31,13 @@ final class EqualityTemplatesTest implements RefasterTemplateTestCase {
return !!Boolean.TRUE;
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testNegation() {
return ImmutableSet.of(
Boolean.TRUE ? !Boolean.FALSE : Boolean.FALSE,
!(Boolean.TRUE == Boolean.FALSE),
true ? !false : false,
!(true == false),
!((byte) 3 == (byte) 4),
!((char) 3 == (char) 4),
!((short) 3 == (short) 4),
!(3 == 4),
!(3L == 4L),
@@ -44,11 +46,13 @@ final class EqualityTemplatesTest implements RefasterTemplateTestCase {
!(BoundType.OPEN == BoundType.CLOSED));
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testIndirectDoubleNegation() {
return ImmutableSet.of(
Boolean.TRUE ? Boolean.FALSE : !Boolean.FALSE,
!(Boolean.TRUE != Boolean.FALSE),
true ? false : !false,
!(true != false),
!((byte) 3 != (byte) 4),
!((char) 3 != (char) 4),
!((short) 3 != (short) 4),
!(3 != 4),
!(3L != 4L),

View File

@@ -31,11 +31,13 @@ final class EqualityTemplatesTest implements RefasterTemplateTestCase {
return Boolean.TRUE;
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testNegation() {
return ImmutableSet.of(
Boolean.TRUE != Boolean.FALSE,
Boolean.TRUE != Boolean.FALSE,
true != false,
true != false,
(byte) 3 != (byte) 4,
(char) 3 != (char) 4,
(short) 3 != (short) 4,
3 != 4,
3L != 4L,
@@ -44,11 +46,13 @@ final class EqualityTemplatesTest implements RefasterTemplateTestCase {
BoundType.OPEN != BoundType.CLOSED);
}
@SuppressWarnings("SimplifyBooleanExpression")
ImmutableSet<Boolean> testIndirectDoubleNegation() {
return ImmutableSet.of(
Boolean.TRUE == Boolean.FALSE,
Boolean.TRUE == Boolean.FALSE,
true == false,
true == false,
(byte) 3 == (byte) 4,
(char) 3 == (char) 4,
(short) 3 == (short) 4,
3 == 4,
3L == 4L,

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refastertemplates;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import java.util.Objects;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -11,6 +12,14 @@ final class NullTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(MoreObjects.class);
}
boolean testIsNull() {
return Objects.isNull("foo");
}
boolean testIsNotNull() {
return Objects.nonNull("foo");
}
String testRequireNonNullElse() {
return MoreObjects.firstNonNull("foo", "bar");
}

View File

@@ -14,6 +14,14 @@ final class NullTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(MoreObjects.class);
}
boolean testIsNull() {
return "foo" == null;
}
boolean testIsNotNull() {
return "foo" != null;
}
String testRequireNonNullElse() {
return requireNonNullElse("foo", "bar");
}

View File

@@ -101,4 +101,12 @@ final class OptionalTemplatesTest implements RefasterTemplateTestCase {
Optional.of("baz").map(Optional::of).orElseGet(() -> Optional.of("qux")),
Stream.of(Optional.of("quux"), Optional.of("quuz")).flatMap(Optional::stream).findFirst());
}
ImmutableSet<Optional<String>> testOptionalIdentity() {
return ImmutableSet.of(
Optional.of("foo").stream().findFirst(),
Optional.of("bar").stream().findAny(),
Optional.of("baz").stream().min(String::compareTo),
Optional.of("qux").stream().max(String::compareTo));
}
}

View File

@@ -98,4 +98,9 @@ final class OptionalTemplatesTest implements RefasterTemplateTestCase {
Optional.of("baz").or(() -> Optional.of("qux")),
Optional.of("quux").or(() -> Optional.of("quuz")));
}
ImmutableSet<Optional<String>> testOptionalIdentity() {
return ImmutableSet.of(
Optional.of("foo"), Optional.of("bar"), Optional.of("baz"), Optional.of("qux"));
}
}

View File

@@ -13,6 +13,7 @@ final class PrimitiveTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testLessThan() {
return ImmutableSet.of(
!((byte) 3 >= (byte) 4),
!((char) 3 >= (char) 4),
!((short) 3 >= (short) 4),
!(3 >= 4),
!(3L >= 4L),
@@ -23,6 +24,7 @@ final class PrimitiveTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testLessThanOrEqualTo() {
return ImmutableSet.of(
!((byte) 3 > (byte) 4),
!((char) 3 > (char) 4),
!((short) 3 > (short) 4),
!(3 > 4),
!(3L > 4L),
@@ -33,6 +35,7 @@ final class PrimitiveTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testGreaterThan() {
return ImmutableSet.of(
!((byte) 3 <= (byte) 4),
!((char) 3 <= (char) 4),
!((short) 3 <= (short) 4),
!(3 <= 4),
!(3L <= 4L),
@@ -43,6 +46,7 @@ final class PrimitiveTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testGreaterThanOrEqualTo() {
return ImmutableSet.of(
!((byte) 3 < (byte) 4),
!((char) 3 < (char) 4),
!((short) 3 < (short) 4),
!(3 < 4),
!(3L < 4L),

View File

@@ -12,22 +12,46 @@ final class PrimitiveTemplatesTest implements RefasterTemplateTestCase {
ImmutableSet<Boolean> testLessThan() {
return ImmutableSet.of(
(byte) 3 < (byte) 4, (short) 3 < (short) 4, 3 < 4, 3L < 4L, 3F < 4F, 3.0 < 4.0);
(byte) 3 < (byte) 4,
(char) 3 < (char) 4,
(short) 3 < (short) 4,
3 < 4,
3L < 4L,
3F < 4F,
3.0 < 4.0);
}
ImmutableSet<Boolean> testLessThanOrEqualTo() {
return ImmutableSet.of(
(byte) 3 <= (byte) 4, (short) 3 <= (short) 4, 3 <= 4, 3L <= 4L, 3F <= 4F, 3.0 <= 4.0);
(byte) 3 <= (byte) 4,
(char) 3 <= (char) 4,
(short) 3 <= (short) 4,
3 <= 4,
3L <= 4L,
3F <= 4F,
3.0 <= 4.0);
}
ImmutableSet<Boolean> testGreaterThan() {
return ImmutableSet.of(
(byte) 3 > (byte) 4, (short) 3 > (short) 4, 3 > 4, 3L > 4L, 3F > 4F, 3.0 > 4.0);
(byte) 3 > (byte) 4,
(char) 3 > (char) 4,
(short) 3 > (short) 4,
3 > 4,
3L > 4L,
3F > 4F,
3.0 > 4.0);
}
ImmutableSet<Boolean> testGreaterThanOrEqualTo() {
return ImmutableSet.of(
(byte) 3 >= (byte) 4, (short) 3 >= (short) 4, 3 >= 4, 3L >= 4L, 3F >= 4F, 3.0 >= 4.0);
(byte) 3 >= (byte) 4,
(char) 3 >= (char) 4,
(short) 3 >= (short) 4,
3 >= 4,
3L >= 4L,
3F >= 4F,
3.0 >= 4.0);
}
int testLongToIntExact() {

View File

@@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -19,6 +20,15 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(assertThat(0));
}
ImmutableSet<Mono<?>> testMonoFromSupplier() {
return ImmutableSet.of(
Mono.fromCallable((Callable<?>) null),
Mono.fromCallable(() -> getClass().getDeclaredConstructor()),
Mono.fromCallable(() -> toString()),
Mono.fromCallable(getClass()::getDeclaredConstructor),
Mono.fromCallable(this::toString));
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.fromCallable(() -> Optional.of(1).orElse(null)),

View File

@@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -20,6 +21,15 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(assertThat(0));
}
ImmutableSet<Mono<?>> testMonoFromSupplier() {
return ImmutableSet.of(
Mono.fromCallable((Callable<?>) null),
Mono.fromCallable(() -> getClass().getDeclaredConstructor()),
Mono.fromSupplier(() -> toString()),
Mono.fromCallable(getClass()::getDeclaredConstructor),
Mono.fromSupplier(this::toString));
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.defer(() -> Mono.justOrEmpty(Optional.of(1))),

View File

@@ -7,8 +7,6 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.util.Arrays;
import org.reactivestreams.Publisher;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -21,24 +19,20 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Completable.complete().to(RxJava2Adapter::completableToMono));
}
ImmutableSet<Publisher<Integer>> testFlowableToFlux() {
// The `Arrays.asList` is to avoid confusing `javac`; `ImmutableSet.of` uses varargs from the
// seventh parameter onwards.
return ImmutableSet.copyOf(
Arrays.asList(
Flowable.just(1).compose(Flux::from),
Flowable.just(2).to(Flux::from),
Flowable.just(3).as(Flux::from),
Flowable.just(4).compose(RxJava2Adapter::flowableToFlux),
Flowable.just(5).<Publisher<Integer>>to(RxJava2Adapter::flowableToFlux)));
ImmutableSet<Flux<Integer>> testFlowableToFlux() {
return ImmutableSet.of(
Flux.from(Flowable.just(1)),
Flowable.just(2).to(Flux::from),
Flowable.just(3).as(Flux::from),
RxJava2Adapter.flowableToFlux(Flowable.just(4)),
Flowable.just(5).to(RxJava2Adapter::flowableToFlux));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
ImmutableSet<Flowable<String>> testFluxToFlowable() {
return ImmutableSet.of(
Flowable.fromPublisher(Flux.just("foo")),
Flux.just("bar").transform(Flowable::fromPublisher),
Flux.just("baz").as(Flowable::fromPublisher),
Flux.just("qux").transform(RxJava2Adapter::fluxToFlowable));
Flux.just("bar").as(Flowable::fromPublisher),
RxJava2Adapter.fluxToFlowable(Flux.just("baz")));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -61,12 +55,11 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
RxJava2Adapter.monoToCompletable(Mono.empty()));
}
ImmutableSet<Publisher<Integer>> testMonoToFlowable() {
ImmutableSet<Flowable<Integer>> testMonoToFlowable() {
return ImmutableSet.of(
Flowable.fromPublisher(Mono.just(1)),
Mono.just(2).transform(Flowable::fromPublisher),
Mono.just(3).as(Flowable::fromPublisher),
Mono.just(4).transform(RxJava2Adapter::monoToFlowable));
Mono.just(2).as(Flowable::fromPublisher),
RxJava2Adapter.monoToFlowable(Mono.just(3)));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -7,8 +7,6 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.util.Arrays;
import org.reactivestreams.Publisher;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -21,24 +19,20 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Completable.complete().as(RxJava2Adapter::completableToMono));
}
ImmutableSet<Publisher<Integer>> testFlowableToFlux() {
// The `Arrays.asList` is to avoid confusing `javac`; `ImmutableSet.of` uses varargs from the
// seventh parameter onwards.
return ImmutableSet.copyOf(
Arrays.asList(
Flowable.just(1).as(RxJava2Adapter::flowableToFlux),
Flowable.just(2).as(RxJava2Adapter::flowableToFlux),
Flowable.just(3).as(RxJava2Adapter::flowableToFlux),
Flowable.just(4).as(RxJava2Adapter::flowableToFlux),
Flowable.just(5).as(RxJava2Adapter::flowableToFlux)));
ImmutableSet<Flux<Integer>> testFlowableToFlux() {
return ImmutableSet.of(
Flowable.just(1).as(RxJava2Adapter::flowableToFlux),
Flowable.just(2).as(RxJava2Adapter::flowableToFlux),
Flowable.just(3).as(RxJava2Adapter::flowableToFlux),
Flowable.just(4).as(RxJava2Adapter::flowableToFlux),
Flowable.just(5).as(RxJava2Adapter::flowableToFlux));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
ImmutableSet<Flowable<String>> testFluxToFlowable() {
return ImmutableSet.of(
Flux.just("foo").as(RxJava2Adapter::fluxToFlowable),
Flux.just("bar").as(RxJava2Adapter::fluxToFlowable),
Flux.just("baz").as(RxJava2Adapter::fluxToFlowable),
Flux.just("qux").as(RxJava2Adapter::fluxToFlowable));
Flux.just("baz").as(RxJava2Adapter::fluxToFlowable));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -61,12 +55,11 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Mono.empty().as(RxJava2Adapter::monoToCompletable));
}
ImmutableSet<Publisher<Integer>> testMonoToFlowable() {
ImmutableSet<Flowable<Integer>> testMonoToFlowable() {
return ImmutableSet.of(
Mono.just(1).as(RxJava2Adapter::monoToFlowable),
Mono.just(2).as(RxJava2Adapter::monoToFlowable),
Mono.just(3).as(RxJava2Adapter::monoToFlowable),
Mono.just(4).as(RxJava2Adapter::monoToFlowable));
Mono.just(3).as(RxJava2Adapter::monoToFlowable));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -10,7 +10,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -18,7 +20,7 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Joiner.class, Stream.class, Streams.class, joining(), UTF_8);
Arrays.class, Joiner.class, Objects.class, Stream.class, Streams.class, joining(), UTF_8);
}
ImmutableSet<Boolean> testStringIsEmpty() {
@@ -59,6 +61,14 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
ImmutableList.of("foo", "bar").stream().collect(joining("f")));
}
String testStringValueOf() {
return Objects.toString("foo");
}
Function<Object, String> testStringValueOfMethodReference() {
return Objects::toString;
}
String testSubstringRemainder() {
return "foo".substring(1, "foo".length());
}

View File

@@ -11,7 +11,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -19,7 +21,7 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Joiner.class, Stream.class, Streams.class, joining(), UTF_8);
Arrays.class, Joiner.class, Objects.class, Stream.class, Streams.class, joining(), UTF_8);
}
ImmutableSet<Boolean> testStringIsEmpty() {
@@ -59,6 +61,14 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
String.join("f", ImmutableList.of("foo", "bar")));
}
String testStringValueOf() {
return String.valueOf("foo");
}
Function<Object, String> testStringValueOfMethodReference() {
return String::valueOf;
}
String testSubstringRemainder() {
return "foo".substring(1);
}

98
generate-docs.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
WEBSITE_FOLDER="website"
DOCS_FOLDER="docs"
BUGPATTERN_FOLDER="${WEBSITE_FOLDER}/bugpatterns"
BUGPATTERN_DOCS_FOLDER="${DOCS_FOLDER}/bugpatterns"
REFASTER_FOLDER="${WEBSITE_FOLDER}/refasterrules"
REFASTER_DOCS_FOLDER="${DOCS_FOLDER}/refasterrules"
HOMEPAGE="${WEBSITE_FOLDER}/index.md"
configure() {
cd "$(git rev-parse --show-toplevel || echo .)"
mkdir "${BUGPATTERN_FOLDER}" 2>/dev/null
mkdir "${REFASTER_FOLDER}" 2>/dev/null
}
generate_homepage() {
echo "Generating ${HOMEPAGE}"
cat > "${HOMEPAGE}" << EOF
---
# Do not modify. This file is generated.
layout: default
title: Home
nav_order: 1
---
EOF
cat "README.md" >> ${HOMEPAGE}
SEDOPTION="-i"
if [[ "$OSTYPE" == "darwin"* ]]; then
SEDOPTION="-i .bak"
fi
sed $SEDOPTION 's/src="/src="assets\/images\//g' ${HOMEPAGE}
sed $SEDOPTION 's/srcset="/srcset="assets\/images\//g' ${HOMEPAGE}
}
generate_bugpattern_docs() {
# The "mvn clean" is necessary since the wiki docs are generated by an
# annotation processor that also compiles the code. If Maven thinks the code
# does not need to be recompiled, the wiki docs will not be generated either.
mvn clean
# This will create markdown files for each bug pattern in `docgen/target/generated-wiki/bugpatterns`
mvn -P run-annotation-processor compile site -Dverification.skip
cp -r docgen/target/generated-wiki/bugpatterns/*.md website/bugpatterns
}
generate_refaster_docs() {
TEMPLATES=$(find error-prone-contrib/src/main/java/tech/picnic/errorprone/refastertemplates -type f -iname "*.java" ! -iname "package-info.java")
for TEMPLATE in $TEMPLATES; do
NAME=$(basename "${TEMPLATE}" ".java")
FILENAME="${REFASTER_FOLDER}/${NAME}.md"
EXTRA_DOCS=$(cat "${REFASTER_DOCS_FOLDER}/${NAME}.md" 2>/dev/null)
echo "Generating ${FILENAME}"
cat > "${FILENAME}" << EOF
---
layout: default
title: ${NAME}
parent: Refaster Rules
nav_order: 1
---
# ${NAME}
Style
{: .label .label-blue }
Error
{: .label .label-red }
${EXTRA_DOCS}
## Samples
\`\`\`java
public static void sample() {}
\`\`\`
<a href="https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/${NAME}.java" class="fs-3 btn external" target="_blank">
View source code on GitHub
<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title"><use xlink:href="#svg-external-link"></use></svg>
</a>
EOF
done
}
# Do it
configure
generate_homepage
generate_bugpattern_docs
generate_refaster_docs

10
logo-dark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

10
logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

116
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -39,6 +39,8 @@
</developers>
<modules>
<module>docgen</module>
<module>docgen_processor</module>
<module>error-prone-contrib</module>
<module>refaster-compiler</module>
<module>refaster-runner</module>
@@ -56,8 +58,8 @@
<url>https://github.com/PicnicSupermarket/error-prone-support/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/PicnicSupermarket/error-prone-support</url>
<system>GitHub Actions</system>
<url>https://github.com/PicnicSupermarket/error-prone-support/actions</url>
</ciManagement>
<distributionManagement>
<repository>
@@ -87,6 +89,7 @@
-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
@@ -94,7 +97,6 @@
--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
<!-- The test JVMs are short-running. By disabling certain
expensive JIT optimizations we actually speed up most tests. -->
@@ -144,16 +146,15 @@
<version.auto-service>1.0.1</version.auto-service>
<version.auto-value>1.9</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-2</version.error-prone-fork>
<version.error-prone-orig>2.14.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.13</version.error-prone-slf4j>
<version.findbugs-format-string>3.0.0</version.findbugs-format-string>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-3</version.error-prone-fork>
<version.error-prone-orig>2.15.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.15</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.8.6</version.maven>
<version.mockito>4.7.0</version.mockito>
<version.mockito>4.8.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.9.9</version.nullaway>
<version.nullaway>0.10.1</version.nullaway>
<!-- XXX: Two other dependencies are potentially of interest:
`com.palantir.assertj-automation:assertj-refaster-rules` and
`com.palantir.baseline:baseline-refaster-rules` contain Refaster rules
@@ -216,7 +217,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.13.3</version>
<version>2.13.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -240,23 +241,11 @@
<artifactId>auto-value-annotations</artifactId>
<version>${version.auto-value}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jFormatString</artifactId>
<version>${version.findbugs-format-string}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<version>9+181-r4173-1</version>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
@@ -291,7 +280,7 @@
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<version>7.9.0</version>
<version>7.10.0</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
@@ -317,7 +306,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.22</version>
<version>2020.0.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -379,7 +368,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.24.0</version>
<version>3.25.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -389,7 +378,12 @@
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<version>2.9.0</version>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
@@ -405,11 +399,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@@ -418,7 +407,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.22</version>
<version>5.3.23</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -527,7 +516,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<version>3.2.0</version>
<configuration>
<checkstyleRules>
<!-- We only enable rules that are not enforced by
@@ -746,6 +735,7 @@
<module name="VisibilityModifier" />
</module>
<module name="UniqueProperties" />
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck" />
</module>
</checkstyleRules>
<failOnViolation>false</failOnViolation>
@@ -769,7 +759,12 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.3.2</version>
<version>10.3.3</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
<artifactId>nohttp-checkstyle</artifactId>
<version>0.0.10</version>
</dependency>
</dependencies>
<executions>
@@ -791,7 +786,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<!-- XXX: MCOMPILER-503: The plugin contructs a highly
<!-- XXX: MCOMPILER-503: The plugin constructs a highly
unintuitive annotation processor classpath, in which
some indirect dependencies take precedence over
explicitly defined dependencies. This can largely be
@@ -851,13 +846,19 @@
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-Xmaxerrs</arg>
<arg>10000</arg>
<arg>-Xmaxwarns</arg>
<arg>10000</arg>
</compilerArgs>
<parameters>true</parameters>
<release>${version.jdk}</release>
<source>${version.jdk}</source>
<target>${version.jdk}</target>
<!-- Erroneously inverted logic... for details, see
https://issues.apache.org/jira/browse/MCOMPILER-209. -->
<useIncrementalCompilation>false</useIncrementalCompilation>
@@ -959,7 +960,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<version>3.3.0</version>
<configuration>
<skipIfEmpty>true</skipIfEmpty>
<archive>
@@ -1203,7 +1204,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.11.0</version>
<version>2.12.0</version>
</plugin>
<plugin>
<groupId>org.gaul</groupId>
@@ -1245,7 +1246,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.9.4</version>
<version>1.9.5</version>
<configuration>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
@@ -1719,41 +1720,6 @@
</pluginManagement>
</build>
</profile>
<profile>
<!-- Some code in this project interfaces directly with the Java
compiler. The following `add-exports` arguments are not necessary
for the code to compile because `com.google.errorprone:javac` is on
the classpath. In fact, enabling this profile when building with
Maven on the command line will cause a build failure, because these
flags are incompatible with the `release` flag. This profile exists
solely to be enabled within an IDE: without them IntelliJ IDEA
reports compilation errors. -->
<id>add-exports</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs combine.children="append">
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
<profile>
<!-- This profile is auto-activated when performing a release. -->
<id>release</id>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-compiler</artifactId>
@@ -37,11 +37,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-runner</artifactId>
@@ -47,11 +47,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-support</artifactId>
@@ -52,8 +52,8 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>

View File

@@ -6,7 +6,7 @@ import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
/** A matcher of array-typed expressions, for use with Refaster's {@code @Matches} annotation. */
/** A matcher of array-typed expressions. */
public final class IsArray implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DELEGATE = isArrayType();

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.refaster.util;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.suppliers.Suppliers.CHAR_TYPE;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
/** A matcher of {@code char}- and {@link Character}-typed expressions. */
public final class IsCharacter implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DELEGATE =
anyOf(isSameType(CHAR_TYPE), isSameType(Character.class));
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
return DELEGATE.matches(tree, state);
}
}

View File

@@ -0,0 +1,60 @@
package tech.picnic.errorprone.refaster.util;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types.FunctionDescriptorLookupError;
import java.util.Collection;
/**
* A matcher of functional interface expressions for which execution of the functional interface
* method may throw a checked exception.
*/
public final class ThrowsCheckedException implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
@Override
public boolean matches(ExpressionTree tree, VisitorState state) {
return containsCheckedException(getThrownTypes(tree, state), state);
}
private static Collection<Type> getThrownTypes(ExpressionTree tree, VisitorState state) {
if (tree instanceof LambdaExpressionTree) {
return ASTHelpers.getThrownExceptions(((LambdaExpressionTree) tree).getBody(), state);
}
if (tree instanceof MemberReferenceTree) {
Symbol symbol = ASTHelpers.getSymbol(tree);
if (symbol == null) {
return ImmutableSet.of();
}
return symbol.type.getThrownTypes();
}
Type type = ASTHelpers.getType(tree);
if (type == null) {
return ImmutableSet.of();
}
try {
return state.getTypes().findDescriptorType(type).getThrownTypes();
} catch (FunctionDescriptorLookupError e) {
return ImmutableSet.of();
}
}
private static boolean containsCheckedException(Collection<Type> types, VisitorState state) {
return !types.stream()
.allMatch(
t ->
ASTHelpers.isSubtype(t, state.getSymtab().runtimeExceptionType, state)
|| ASTHelpers.isSubtype(t, state.getSymtab().errorType, state));
}
}

View File

@@ -1,7 +1,8 @@
/**
* A collection of zero-argument {@link com.google.errorprone.matchers.Matcher Matcher}
* implementations for use with Refaster's {@link
* com.google.errorprone.refaster.annotation.Matches @Matches} annotation.
* com.google.errorprone.refaster.annotation.Matches @Matches} and {@link
* com.google.errorprone.refaster.annotation.NotMatches @NotMatches} annotations.
*/
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault

View File

@@ -0,0 +1,70 @@
package tech.picnic.errorprone.refaster.util;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreeScanner;
import javax.annotation.Nullable;
/**
* An abstract {@link BugChecker} that reports a match for each expression matched by the given
* {@link Matcher}.
*
* <p>Only {@link ExpressionTree}s that represent proper Java expressions (i.e. {@link
* ExpressionTree}s that may be matched by Refaster) are considered.
*/
abstract class AbstractMatcherTestChecker extends BugChecker implements CompilationUnitTreeMatcher {
private static final long serialVersionUID = 1L;
private final Matcher<ExpressionTree> delegate;
AbstractMatcherTestChecker(Matcher<ExpressionTree> delegate) {
this.delegate = delegate;
}
@Override
public Description matchCompilationUnit(CompilationUnitTree compilationUnit, VisitorState state) {
new TreeScanner<Void, Void>() {
@Nullable
@Override
public Void scan(Tree tree, @Nullable Void unused) {
if (tree instanceof ExpressionTree && delegate.matches((ExpressionTree) tree, state)) {
state.reportMatch(
Description.builder(tree, canonicalName(), null, defaultSeverity(), message())
.build());
}
return super.scan(tree, unused);
}
@Nullable
@Override
public Void visitImport(ImportTree node, @Nullable Void unused) {
/*
* We're not interested in matching import statements. While components of these
* can be `ExpressionTree`s, they will never be matched by Refaster.
*/
return null;
}
@Nullable
@Override
public Void visitMethod(MethodTree node, @Nullable Void unused) {
/*
* We're not interested in matching e.g. parameter and return type declarations. While these
* can be `ExpressionTree`s, they will never be matched by Refaster.
*/
return scan(node.getBody(), unused);
}
}.scan(compilationUnit, null);
return Description.NO_MATCH;
}
}

View File

@@ -4,17 +4,13 @@ import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodInvocationTree;
import org.junit.jupiter.api.Test;
final class IsArrayTest {
@Test
void matches() {
CompilationTestHelper.newInstance(TestChecker.class, getClass())
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
@@ -53,13 +49,15 @@ final class IsArrayTest {
}
/** A {@link BugChecker} which simply delegates to {@link IsArray}. */
@BugPattern(summary = "Flags array-returning method invocations", severity = ERROR)
public static final class TestChecker extends BugChecker implements MethodInvocationTreeMatcher {
@BugPattern(summary = "Flags expressions matched by `IsArray`", severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return new IsArray().matches(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
// XXX: This is a false positive reported by Checkstyle. See
// https://github.com/checkstyle/checkstyle/issues/10161#issuecomment-1242732120.
@SuppressWarnings("RedundantModifier")
public MatcherTestChecker() {
super(new IsArray());
}
}
}

View File

@@ -0,0 +1,63 @@
package tech.picnic.errorprone.refaster.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.bugpatterns.BugChecker;
import org.junit.jupiter.api.Test;
final class IsCharacterTest {
@Test
void matches() {
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" String negative1() {",
" return \"a\";",
" }",
"",
" char[] negative2() {",
" return \"a\".toCharArray();",
" }",
"",
" byte negative3() {",
" return (byte) 0;",
" }",
"",
" int negative4() {",
" return 0;",
" }",
"",
" char positive1() {",
" // BUG: Diagnostic contains:",
" return 'a';",
" }",
"",
" Character positive2() {",
" // BUG: Diagnostic contains:",
" return (Character) null;",
" }",
"",
" char positive3() {",
" // BUG: Diagnostic contains:",
" return (char) 1;",
" }",
"}")
.doTest();
}
/** A {@link BugChecker} which simply delegates to {@link IsCharacter}. */
@BugPattern(summary = "Flags expressions matched by `IsCharacter`", severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;
// XXX: This is a false positive reported by Checkstyle. See
// https://github.com/checkstyle/checkstyle/issues/10161#issuecomment-1242732120.
@SuppressWarnings("RedundantModifier")
public MatcherTestChecker() {
super(new IsCharacter());
}
}
}

View File

@@ -0,0 +1,94 @@
package tech.picnic.errorprone.refaster.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.bugpatterns.BugChecker;
import org.junit.jupiter.api.Test;
final class ThrowsCheckedExceptionTest {
@Test
void matches() {
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import java.util.concurrent.Callable;",
"import java.util.function.Supplier;",
"",
"class A {",
" void negative1() {",
" callableSink(null);",
" }",
"",
" void negative2() {",
" supplierSink(null);",
" }",
"",
" void negative3() {",
" callableSink(() -> toString());",
" }",
"",
" void negative4() {",
" supplierSink(() -> toString());",
" }",
"",
" void negative5() {",
" callableSink(this::toString);",
" }",
"",
" void negative6() {",
" supplierSink(this::toString);",
" }",
"",
" void negative7() {",
" supplierSink(",
" new Supplier<>() {",
" @Override",
" public Object get() {",
" return getClass();",
" }",
" });",
" }",
"",
" void positive1() {",
" // BUG: Diagnostic contains:",
" callableSink(() -> getClass().getDeclaredConstructor());",
" }",
"",
" void positive2() {",
" // BUG: Diagnostic contains:",
" callableSink(getClass()::getDeclaredConstructor);",
" }",
"",
" void positive3() {",
" callableSink(",
" // BUG: Diagnostic contains:",
" new Callable<>() {",
" @Override",
" public Object call() throws NoSuchMethodException {",
" return getClass().getDeclaredConstructor();",
" }",
" });",
" }",
"",
" private static void callableSink(Callable<?> callable) {}",
"",
" private static void supplierSink(Supplier<?> supplier) {}",
"}")
.doTest();
}
/** A {@link BugChecker} which simply delegates to {@link ThrowsCheckedException}. */
@BugPattern(summary = "Flags expressions matched by `ThrowsCheckedException`", severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;
// XXX: This is a false positive reported by Checkstyle. See
// https://github.com/checkstyle/checkstyle/issues/10161#issuecomment-1242732120.
@SuppressWarnings("RedundantModifier")
public MatcherTestChecker() {
super(new ThrowsCheckedException());
}
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-test-support</artifactId>
@@ -50,11 +50,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -7,7 +7,6 @@ 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.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
@@ -30,7 +29,7 @@ final class ValidTemplates {
static final class StaticImportStringLength {
@BeforeTemplate
boolean before(@Nullable String string) {
return Objects.isNull(string) || string.isEmpty();
return string == null || string.toCharArray().length == 0;
}
@AfterTemplate

View File

@@ -3,14 +3,13 @@ package tech.picnic.errorprone.refaster.test;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/** Code to test the Refaster templates from {@link ValidTemplates}. */
final class ValidTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Strings.class);
return ImmutableSet.of(Strings.class);
}
boolean testStringIsEmpty2() {
@@ -18,7 +17,7 @@ final class ValidTemplatesTest implements RefasterTemplateTestCase {
}
boolean testStaticImportStringLength() {
return Objects.isNull("foo") || "foo".isEmpty();
return "foo" == null || "foo".toCharArray().length == 0;
}
void testBlockTemplateSetAddElement() {

View File

@@ -5,14 +5,13 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/** Code to test the Refaster templates from {@link ValidTemplates}. */
final class ValidTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Strings.class);
return ImmutableSet.of(Strings.class);
}
boolean testStringIsEmpty2() {

16
website/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
### Jekyll ###
_site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
Gemfile.lock
# Ignore folders generated by Bundler
.bundle/
vendor/
# Generated content
*.bak
index.md
bugpatterns/
refasterrules/

9
website/404.md Normal file
View File

@@ -0,0 +1,9 @@
---
layout: default
nav_exclude: true
search_exclude: true
---
## Page not found :(
The requested page could not be found.

4
website/Gemfile Normal file
View File

@@ -0,0 +1,4 @@
source "https://rubygems.org"
gem "github-pages", "~> 227"
gem "rake", "~> 13.0" # Required for "just-the-docs" theme
gem "webrick", "~> 1.7"

44
website/README.md Normal file
View File

@@ -0,0 +1,44 @@
# Error Prone Support website
This directory contains the majority of the source code that powers
[error-prone.picnic.tech][error-prone-support-website]. The website is
statically generated using [Jekyll][jekyll].
# Local development
To view the website on `localhost`, first follow the [Jekyll installation
instructions][jekyll-docs-installation]. Once done, in this directory execute:
```sh
bundle install
../generate-docs.sh && bundle exec jekyll serve --livereload
```
The website will now be [available][localhost-port-4000] on port 4000. Source
code modifications will automatically be reflected. (An exception is
`_config.yml`: changes to this file require a server restart.) Subsequent
server restarts do not require running `bundle install`, unless `Gemfile` has
been updated in the interim.
Documentation can be regenerated whist jekyll is running, by executing:
```sh
../generate-docs.sh
```
If you are not familiar with Jekyll, be sure to check out its
[documentation][jekyll-docs]. It is recommended to follow the provided
step-by-step tutorial.
# Deployment
The website is regenerated and deployed using the
[`deploy-website.yaml`][error-prone-support-website-deploy-workflow] GitHub
Actions workflow any time a change is merged to `master`.
[error-prone-support-website]: https://error-prone.picnic.tech
[error-prone-support-website-deploy-workflow]: https://github.com/PicnicSupermarket/error-prone-support/actions/workflows/deploy-website.yaml
[jekyll]: https://jekyllrb.com
[jekyll-docs]: https://jekyllrb.com/docs/
[jekyll-docs-installation]: https://jekyllrb.com/docs/installation/
[localhost-port-4000]: http://127.0.0.1:4000

49
website/_config.yml Normal file
View File

@@ -0,0 +1,49 @@
# General configuration.
title: Error Prone Support
logo: assets/images/favicon.svg
url: https://error-prone.picnic.tech/
description: >-
Error Prone Support is a Picnic-opinionated extension of Google's Error
Prone. It aims to improve code quality, focussing on maintainability,
consistency and avoidance of common gotchas.
remote_theme: just-the-docs/just-the-docs
plugins:
- jekyll-remote-theme
# Do not deploy through GitHub pages.
exclude:
- Gemfile
- Gemfile.lock
- README.md
- vendor
permalink: pretty # Use /doc/ vs. /doc.html
# Theme configuration.
search_enabled: true
heading_anchors: true
callouts:
summary:
title: Summary
color: blue
warning:
title: Warning
color: red
nav_external_links:
- title: Error Prone Support on GitHub
url: https://github.com/PicnicSupermarket/error-prone-support
hide_icon: false
twitter:
username: picnic
card: summary
social:
name: Picnic
links:
- https://github.com/PicnicSupermarket
- https://twitter.com/picnic
- https://www.linkedin.com/company/picnictechnologies

View File

@@ -0,0 +1,5 @@
<img src="/assets/images/picnic-logo@2x.png" alt="Picnic Logo" id="logo" />
<p align="center">
Copyright &copy; 2017-2022 Picnic Technologies BV
</p>

View File

@@ -0,0 +1,17 @@
<!-- Generated from https://realfavicongenerator.net/ -->
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
<link rel="manifest" href="/assets/images/site.webmanifest">
<link rel="mask-icon" href="/assets/images/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/assets/images/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- Support light and dark mode, as it's not natively supported.
See: https://github.com/just-the-docs/just-the-docs/issues/234 -->
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-light.css' | relative_url }}"
media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-dark.css' | relative_url }}"
media="(prefers-color-scheme: dark)">

View File

@@ -0,0 +1,24 @@
// We should override $nav-width(-md), however this breaks code highlighting and other styles.
// This appears an issue wrt the recommended way:
// https://github.com/just-the-docs/just-the-docs/issues/982
@include mq(lg) {
.side-bar {
min-width: 400px;
}
.site-nav, .site-header {
width: 400px;
}
}
// Add support for external anchor icons.
.external > svg {
width: 1rem;
vertical-align: text-bottom;
}
footer > img#logo {
width: 2rem;
margin: 0 auto;
display: block;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/assets/images/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="2623.000000pt" height="2623.000000pt" viewBox="0 0 2623.000000 2623.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,2623.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M12907 25594 c-1 -1 -87 -5 -190 -9 -104 -3 -207 -8 -230 -10 -23 -2
-78 -7 -122 -10 -44 -3 -109 -8 -145 -11 -36 -2 -78 -6 -95 -9 -16 -2 -59 -6
-95 -10 -36 -3 -76 -8 -90 -10 -14 -2 -50 -7 -80 -10 -30 -4 -66 -8 -80 -10
-14 -2 -61 -9 -105 -15 -44 -6 -91 -13 -105 -16 -14 -2 -41 -7 -60 -10 -461
-71 -1060 -217 -1545 -378 -124 -41 -379 -132 -410 -146 -11 -5 -67 -28 -125
-51 -505 -197 -1190 -545 -1630 -827 -82 -52 -373 -246 -385 -255 -5 -5 -53
-38 -105 -75 -239 -168 -595 -450 -796 -631 -27 -25 -93 -84 -145 -130 -114
-103 -545 -533 -650 -651 -156 -173 -189 -212 -316 -365 -32 -39 -71 -88 -88
-108 -16 -21 -35 -44 -40 -50 -23 -26 -207 -269 -282 -372 -118 -163 -384
-564 -437 -660 -6 -11 -43 -74 -82 -140 -39 -66 -88 -151 -108 -188 -20 -37
-46 -84 -58 -105 -12 -20 -71 -136 -132 -257 -101 -200 -142 -285 -199 -415
-11 -25 -31 -70 -44 -100 -72 -159 -275 -683 -298 -770 -3 -14 -12 -41 -20
-60 -21 -55 -127 -402 -149 -490 -2 -8 -18 -68 -36 -134 -17 -65 -34 -130 -36
-145 -3 -14 -16 -71 -29 -126 -24 -104 -37 -161 -36 -170 0 -2 -4 -19 -9 -36
-5 -18 -12 -50 -16 -73 -3 -22 -7 -43 -9 -46 -2 -4 -7 -29 -11 -56 -3 -27 -8
-52 -10 -55 -2 -4 -6 -24 -9 -45 -3 -22 -10 -61 -15 -89 -6 -27 -12 -68 -15
-90 -3 -22 -8 -51 -10 -65 -3 -14 -7 -42 -10 -62 -3 -21 -7 -55 -10 -75 -3
-21 -7 -49 -10 -63 -2 -14 -7 -52 -10 -85 -3 -33 -7 -71 -10 -85 -5 -28 -13
-106 -20 -195 -3 -33 -7 -78 -10 -100 -2 -22 -7 -83 -10 -135 -4 -52 -9 -122
-11 -155 -3 -33 -7 -269 -10 -525 -3 -256 -9 -468 -12 -472 -4 -3 -29 -19 -57
-35 -203 -116 -785 -512 -935 -636 -8 -7 -65 -51 -125 -97 -119 -92 -382 -308
-455 -374 -25 -23 -79 -70 -120 -106 -273 -240 -657 -632 -850 -870 -320 -394
-512 -753 -546 -1020 -25 -193 12 -365 112 -516 127 -192 309 -298 601 -348
174 -30 530 -26 747 8 17 3 54 8 81 11 28 4 57 8 65 10 8 2 35 7 60 10 25 3
52 8 60 10 8 2 31 7 50 10 19 3 87 17 150 31 63 15 121 27 130 29 36 8 266 69
395 106 109 31 572 182 632 205 8 4 11 -466 11 -1913 0 -1055 3 -1952 6 -1993
41 -532 186 -1020 438 -1475 82 -149 279 -437 355 -520 10 -11 45 -52 78 -90
109 -127 291 -302 440 -423 89 -72 374 -272 389 -272 2 0 33 -18 69 -39 82
-50 369 -191 388 -191 8 0 19 -4 25 -9 9 -9 52 -26 214 -82 182 -64 401 -114
660 -154 27 -4 95 -11 205 -21 135 -12 518 -5 615 10 14 3 52 7 85 11 33 3 69
8 80 10 11 3 37 7 57 10 21 2 57 9 80 14 24 6 57 12 73 15 17 3 66 15 110 26
44 12 88 23 98 25 101 22 417 140 572 214 53 25 82 34 86 27 4 -6 10 -36 13
-66 4 -30 11 -77 16 -105 5 -27 11 -61 14 -75 4 -27 13 -79 20 -120 7 -37 22
-111 51 -245 14 -66 27 -131 30 -145 3 -14 27 -115 55 -225 28 -110 52 -207
54 -215 11 -46 28 -107 33 -120 3 -8 7 -19 8 -25 9 -39 27 -104 35 -125 5 -14
23 -72 40 -130 63 -208 154 -476 227 -665 74 -190 87 -224 145 -360 111 -257
276 -584 372 -735 42 -65 150 -225 155 -230 4 -3 22 -26 41 -53 70 -96 196
-227 288 -301 159 -127 307 -186 481 -191 401 -11 693 267 904 861 58 163 130
425 152 554 3 19 10 53 15 75 5 22 12 60 15 85 4 24 8 47 10 50 1 3 6 28 9 55
4 28 9 57 11 65 2 8 7 36 10 62 5 46 8 73 20 153 6 43 10 75 20 170 4 33 8 71
10 85 1 14 5 61 9 105 4 44 9 96 11 115 4 34 19 257 24 366 1 21 2 21 119 -12
124 -36 334 -88 392 -98 19 -3 46 -8 60 -11 14 -3 36 -7 50 -10 14 -3 34 -7
45 -9 32 -6 226 -35 278 -41 26 -3 66 -7 90 -10 245 -31 756 -36 997 -11 19 2
67 7 105 11 71 6 80 7 170 19 159 21 359 60 575 111 193 45 635 192 712 235
29 16 38 12 81 -37 211 -238 328 -363 532 -568 197 -197 293 -283 429 -385 26
-19 53 -40 60 -47 24 -21 239 -164 311 -206 69 -41 319 -167 330 -167 3 0 24
-8 48 -19 219 -98 513 -170 797 -197 54 -5 382 -5 408 1 13 2 52 7 86 10 68 6
255 38 298 51 9 3 25 6 35 8 86 17 315 89 423 133 251 102 409 200 545 338
149 151 214 297 220 492 9 309 -179 590 -595 887 -138 99 -400 252 -560 326
l-66 31 -53 104 c-28 57 -72 141 -97 186 -24 45 -43 84 -41 86 2 1 28 -7 58
-18 78 -29 296 -97 379 -118 71 -18 221 -49 270 -57 84 -13 75 -11 220 -29
174 -22 537 -22 723 -1 26 3 68 8 94 11 111 12 280 45 416 81 32 8 68 17 80
20 65 12 444 150 504 183 10 6 69 35 131 66 98 48 327 179 342 195 3 3 38 27
78 54 40 27 101 71 135 97 34 27 67 52 72 56 77 57 356 325 440 423 28 32 139
170 157 195 4 5 18 24 31 40 85 108 258 396 337 560 108 226 224 560 266 768
25 125 50 281 59 377 3 25 8 70 12 100 3 30 7 930 8 2000 2 1667 4 1945 16
1942 8 -2 106 -34 219 -72 175 -58 327 -107 365 -116 6 -1 51 -14 100 -29 106
-31 397 -105 440 -112 17 -2 55 -11 85 -18 30 -8 82 -18 115 -24 33 -6 74 -13
90 -16 279 -51 619 -72 848 -52 379 34 643 195 764 465 75 169 78 383 8 599
-38 115 -152 341 -245 482 -183 280 -375 506 -715 846 -198 199 -347 339 -494
465 -36 30 -72 62 -80 70 -9 8 -66 55 -126 105 -61 49 -115 94 -121 100 -6 5
-26 21 -44 35 -18 14 -81 62 -139 107 -133 103 -129 101 -311 235 -165 121
-495 341 -695 462 l-80 48 -1 346 c0 304 -9 625 -19 732 -2 19 -6 78 -10 130
-4 52 -8 108 -10 125 -2 16 -6 59 -9 95 -6 64 -9 88 -21 185 -11 88 -14 115
-19 153 -3 20 -8 57 -11 82 -3 25 -8 56 -10 70 -3 14 -7 40 -10 58 -3 17 -7
44 -10 60 -2 15 -7 43 -10 62 -18 111 -30 174 -65 345 -22 105 -49 228 -60
275 -11 47 -23 95 -25 108 -11 47 -77 293 -115 422 -221 761 -566 1552 -987
2265 -97 163 -104 175 -278 438 -688 1039 -1542 1917 -2595 2667 -133 95 -576
380 -590 380 -6 0 -10 5 -10 10 0 6 -4 10 -9 10 -5 0 -55 27 -111 60 -56 33
-104 60 -107 60 -3 0 -17 8 -31 18 -38 27 -156 89 -397 212 -546 277 -1134
507 -1740 680 -99 29 -187 53 -195 55 -8 2 -89 22 -180 44 -91 23 -178 44
-195 47 -16 3 -46 9 -65 14 -19 5 -51 12 -70 15 -19 3 -42 8 -50 10 -8 2 -26
6 -40 9 -38 6 -142 25 -170 31 -33 7 -127 23 -170 29 -19 3 -48 8 -65 11 -64
11 -92 15 -140 20 -27 3 -75 10 -105 15 -30 5 -86 12 -125 15 -38 4 -77 8 -85
10 -28 5 -171 18 -330 30 -52 4 -111 8 -130 10 -114 10 -835 26 -843 19z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/assets/images/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/images/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Some files were not shown because too many files have changed in this diff Show More