Compare commits

...

316 Commits

Author SHA1 Message Date
Gijs de Jong
2890bb983f [WIP] Expiriment with jq parsing 2022-10-07 14:23:10 +02:00
Gijs de Jong
f7165a84b6 Remove duplicate check 2022-10-07 10:36:23 +02:00
Gijs de Jong
b0ad824db8 Clean up doc extractor 2022-10-07 10:09:58 +02:00
Rick Ossendrijver
e2b79dac47 Support extracting Refaster Test Data 2022-10-06 18:16:39 +02:00
Rick Ossendrijver
dada0f23f1 Move all Refaster template test resources to respective .input and .output packages 2022-10-06 15:55:04 +02:00
Rick Ossendrijver
20ed8568c2 Improve interface and let BugPatternData also implement it 2022-10-06 13:16:39 +02:00
Rick Ossendrijver
546d3e3739 Add support for collecting BugPatternTestData 2022-10-06 08:56:11 +02:00
Rick Ossendrijver
eca96a76d2 Introduce RefasterTemplateData 2022-10-05 10:02:44 +02:00
Rick Ossendrijver
530fd8da1f Introduce Docgen module 2022-10-05 10:02:39 +02:00
Picnic-Bot
c4e476a731 Upgrade Checker Framework Annotations 3.25.0 -> 3.26.0 (#276)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.26.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.25.0...checker-framework-3.26.0
2022-10-04 10:03:16 +02:00
Jelmer Borst
5ca95eb36d Set up documentation website generation and deployment (#253)
Generating the website is done by Jekyll with a theme called `just-the-docs`. 
Deployment of the website is done via GitHub Pages.

See:
- https://github.com/jekyll/jekyll
- https://github.com/just-the-docs/just-the-docs
2022-10-04 09:08:23 +02:00
Picnic-Bot
9204ef0e84 Upgrade pitest-maven-plugin 1.9.5 -> 1.9.6 (#275)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.6
- https://github.com/hcoles/pitest/compare/1.9.5...1.9.6
2022-10-04 08:53:45 +02:00
Picnic-Bot
ed45be4e15 Upgrade Forbidden APIs plugin 3.3 -> 3.4 (#272)
See:
- https://github.com/policeman-tools/forbidden-apis/wiki/Changes
- https://github.com/policeman-tools/forbidden-apis/compare/3.3...3.4
2022-10-03 07:04:08 +02:00
Rick Ossendrijver
0561c371de Emit website link along with BugChecker rewrite suggestions (#251) 2022-10-02 14:12:27 +02:00
Nadir Belarouci
aa5ad4d25b Introduce Comparators{Min,Max} Refaster templates (#270) 2022-09-29 20:35:05 +02:00
Rick Ossendrijver
8c0041a94e Fix typos and grammar in pom.xml (#268) 2022-09-29 16:10:30 +02:00
Stephan Schroevers
397f9c3df7 Suggest canonical modifier usage for Refaster template definitions (#254) 2022-09-29 13:07:56 +02:00
Stephan Schroevers
2ba7bf9f46 Have RefasterTemplateCollection verify template test class names (#233) 2022-09-29 11:53:22 +02:00
Rick Ossendrijver
5b079eef84 Rename package tech.picnic.errorprone.refaster.{util => matchers} (#267) 2022-09-29 10:29:36 +02:00
Picnic-Bot
a2ce053daf Upgrade SLF4J API 2.0.2 -> 2.0.3 (#269)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_2.0.2...v_2.0.3
2022-09-29 08:28:00 +02:00
Picnic-Bot
ba02bad9bf Upgrade pitest-junit5-plugin 1.0.0 -> 1.1.0 (#265)
See https://github.com/pitest/pitest-junit5-plugin/compare/1.0.0...1.1.0
2022-09-28 20:13:53 +02:00
Picnic-Bot
d97a20247f Upgrade swagger-annotations 2.2.2 -> 2.2.3 (#263)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.3
- https://github.com/swagger-api/swagger-core/compare/v2.2.2...v2.2.3
2022-09-28 19:47:13 +02:00
Sander Mak
50970eb932 Apply small README improvements (#266) 2022-09-28 17:21:51 +02:00
Picnic-Bot
7e7318ad80 Upgrade swagger-annotations 1.6.6 -> 1.6.7 (#264)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.7
- https://github.com/swagger-api/swagger-core/compare/v1.6.6...v1.6.7
2022-09-28 08:26:53 +02:00
Rick Ossendrijver
fb6fe5a96e Introduce GitHub issue template for feature requests (#244) 2022-09-27 14:53:33 +02:00
Rick Ossendrijver
e37da2a1ed Drop unnecessary BugCheckerRefactoringTestHelper file path prefixes (#248) 2022-09-27 14:22:52 +02:00
Picnic-Bot
7bef1c8e67 Upgrade actions/setup-java v3.4.1 -> v3.5.1 (#262)
See:
- https://github.com/actions/setup-java/releases/tag/v3.5.0
- https://github.com/actions/setup-java/releases/tag/v3.5.1
- https://github.com/actions/setup-java/compare/v3.4.1...v3.5.1
2022-09-27 07:57:25 +02:00
Picnic-Bot
0160eafca0 Upgrade Checkstyle 10.3.3 -> 10.3.4 (#260)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.4
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.3...checkstyle-10.3.4
2022-09-26 09:01:51 +02:00
Rick Ossendrijver
891fecd297 Replace occurrences of "which" with "that" in defining clauses (#259) 2022-09-25 19:09:22 +02:00
Jelmer Borst
7f18bd9030 Introduce GitHub issue template for reporting a bug (#223) 2022-09-25 11:26:12 +02:00
Picnic-Bot
1473a70de8 Upgrade Spring Boot 2.7.3 -> 2.7.4 (#257)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.4
- https://github.com/spring-projects/spring-boot/compare/v2.7.3...v2.7.4
2022-09-23 13:55:00 +02:00
Rick Ossendrijver
c0c3ce2644 Set project home page to https://error-prone.picnic.tech (#258) 2022-09-23 13:04:46 +02:00
Picnic-Bot
100d5c86f7 Upgrade NullAway 0.10.1 -> 0.10.2 (#256)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.1...v0.10.2
2022-09-22 10:32:30 +02:00
Picnic-Bot
e12f99975b Upgrade SLF4J API 1.7.36 -> 2.0.2 (#209)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_1.7.36...v_2.0.1
2022-09-22 09:00:19 +02:00
Picnic-Bot
791113669f Upgrade JUnit Jupiter 5.9.0 -> 5.9.1 (#252)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.9.1
- https://github.com/junit-team/junit5/compare/r5.9.0...r5.9.1
2022-09-21 16:07:03 +02:00
Stephan Schroevers
564bc7e1d1 Generate reproducible build output (#243)
By deriving `project.build.outputTimestamp` from the timestamp of the 
most recent commit, the timestamp embedded in generated JARs no longer
depends on the exact time at which the artifacts are built. As such
repeated executions of `mvn clean install` yield byte-for-byte identical
results.

This change requires replacing `buildnumber-maven-plugin` with
`git-commit-id-maven-plugin`.

See https://maven.apache.org/guides/mini/guide-reproducible-builds.html
2022-09-21 13:23:02 +02:00
Rick Ossendrijver
43bcbeaa98 Update XXX comments to reference google/error-prone#2706 (#249) 2022-09-21 08:23:09 +02:00
Svava Bjarnadóttir
d682b7d41f Fix typos and grammar in error-prone-contrib/README.md (#250)
While there, drop an obsolete TODO entry.
2022-09-21 07:55:07 +02:00
Stephan Schroevers
72a124a20e [maven-release-plugin] prepare for next development iteration 2022-09-20 13:47:18 +02:00
Stephan Schroevers
326f3328a7 [maven-release-plugin] prepare release v0.3.0 2022-09-20 13:47:16 +02:00
Stephan Schroevers
ae7068f464 Fix Javadoc JAR generation (#246)
For performance reasons, the Javadoc JARs are not generated when executing `mvn
install`. They are generally only compiled when running `mvn release:perform`,
as part of the `release` Maven profile.

The downside of this setup is that Javadoc generation issues may not be caught
until release time. This change resolves such an issue. To prevent future
regressions, Javadoc generation is now also performed by the GitHub Actions
build workflow.

While there, drop the deprecated `useReleaseProfile` configuration setting of
the javadoc-maven-plugin, as its value matches the default.
2022-09-20 13:42:52 +02:00
Stephan Schroevers
066931fe53 Add newline at end of logo.svg and logo-dark.svg (#247)
For compatibility with our internal format script.
2022-09-20 07:39:50 +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
Cernat Catalin Stefan
b8e22ffef0 Introduce NonEmptyMono check (#200) 2022-08-20 12:47:56 +02:00
Picnic-Bot
17035a1623 Upgrade Spring Boot 2.7.2 -> 2.7.3 (#207)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.3
- https://github.com/spring-projects/spring-boot/compare/v2.7.2...v2.7.3
2022-08-20 09:54:57 +02:00
Picnic-Bot
e7dacd19d7 Upgrade Maven API 3.6.3 -> 3.8.6 (#184)
See:
- https://maven.apache.org/release-notes-all.html
- https://github.com/apache/maven/releases/tag/maven-3.8.4
- https://github.com/apache/maven/releases/tag/maven-3.8.5
- https://github.com/apache/maven/releases/tag/maven-3.8.6
- https://github.com/apache/maven/compare/maven-3.6.3...maven-3.8.6
2022-08-19 19:05:18 +02:00
Bastien Diederichs
e64af1dde0 Don't enforce sorting of Spring resource locations (#204)
Because their order matters.
2022-08-19 15:29:21 +02:00
Rick Ossendrijver
a58630bccf Improve StreamMapToOptionalGet Refaster template documentation (#203) 2022-08-19 13:33:23 +02:00
Gijs de Jong
9ab5bbe042 Require static import of com.google.errorprone.matchers.Matchers methods (#201) 2022-08-18 15:21:16 +02:00
Stephan Schroevers
4ca75c6cf6 Enable Error Prone's VoidMissingNullable check (#180) 2022-08-18 14:28:01 +02:00
Ferdinand Swoboda
7883b31eb6 Introduce ImmutablesSortedSetComparator check (#102) 2022-08-18 11:33:20 +02:00
Stephan Schroevers
ef751ce785 Drop various vacuous null checks (#191)
Recent versions of Error Prone guarantee that `ASTHelpers#getSymbol`
never returns `null`.
2022-08-18 11:25:35 +02:00
Vincent Koeman
130c3d0bc3 Introduce NestedOptionals check (#202) 2022-08-17 22:14:31 +02:00
Picnic-Bot
c89e3905bf Upgrade Mockito 4.6.1 -> 4.7.0 (#196)
See:
- https://github.com/mockito/mockito/releases/tag/v4.7.0
- https://github.com/mockito/mockito/compare/v4.6.1...v4.7.0
2022-08-17 16:37:54 +02:00
Picnic-Bot
21421ce753 Upgrade maven-javadoc-plugin 3.4.0 -> 3.4.1 (#195)
See:
- https://github.com/apache/maven-javadoc-plugin/releases/tag/maven-javadoc-plugin-3.4.1
- https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.4.0...maven-javadoc-plugin-3.4.1
2022-08-17 16:00:15 +02:00
Ivan Fedorov
c39d1251d2 Rewrite another ThrowableAssertAlternative#withMessage(String, Object...) expression (#190) 2022-08-17 07:38:22 +02:00
Dario Deya Diaz
9bc732b4fe Have RequestMappingAnnotation recognize @RequestAttribute parameters (#189) 2022-08-12 20:56:28 +02:00
Picnic-Bot
74100b6c41 Upgrade Project Reactor 2020.0.21 -> 2020.0.22 (#187)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.22
- https://github.com/reactor/reactor/compare/2020.0.21...2020.0.22
2022-08-11 08:21:06 +02:00
Stephan Schroevers
624f2ce753 [maven-release-plugin] prepare for next development iteration 2022-08-10 15:06:32 +02:00
Stephan Schroevers
967017eed9 [maven-release-plugin] prepare release v0.1.0 2022-08-10 15:06:30 +02:00
Rick Ossendrijver
b06945b833 Introduce utilities to validate Refaster template collections (#43)
The `Refaster` check is now located in the new `refaster-runner` Maven module.

The new `refaster-test-support` module exposes
`RefasterTemplateCollection#validate`, which for a given template collection
validates:
- That there exist corresponding `*Test{Input,Output}.java` files.
- That each template is exercised by precisely one method in these files.
2022-08-10 14:40:07 +02:00
Rick Ossendrijver
ef562c1644 Rewrite more ThrowableAssertAlternative#withMessage(String, Object...) expressions (#185) 2022-08-10 14:32:44 +02:00
Rick Ossendrijver
efbde936dc Only suggest bulk removal operations on sets (#186)
If a collection may contain duplicates, then removing a single occurrence of
each element in some other set is not equivalent to removing all such
occurrences.
2022-08-10 14:09:36 +02:00
Stephan Schroevers
c57653dd5b Introduce run-mutation-tests.sh (#170)
This script executes Pitest to determine the code base' mutation test coverage.
The set of tests executed can optionally be restricted by name. The results are 
found in each Maven module's `target/pit-reports` directory.
2022-08-09 14:38:19 +02:00
Stephan Schroevers
12b03e95b1 Manage version of org.apache.maven:maven-plugin-api (#183)
By managing the version of this dependency we expect Renovate to file a pull
request any time a new Maven version is released. Through a shared property,
this also enforces that the project is built using the latest Maven release.
2022-08-08 13:57:51 +02:00
Stephan Schroevers
c2f24ac739 Speed up the build (#169)
The build is sped up in two ways:
1. By tweaking the JVM flags passed to the main process.
2. By tweaking the JVM flags passed to the forked Surefire processes.
2022-08-08 13:41:27 +02:00
Rick Ossendrijver
4f5ea8beac Migrate from Travis CI to GitHub Actions (#158)
While there, drop the now-obsolete Takari Maven plugin.
2022-08-07 18:33:25 +02:00
Stephan Schroevers
21646ffcb1 Enable the BugPatternNaming check (#86)
While there, introduce a `.util` subpackage for non-`Bugchecker` classes.
2022-08-07 18:12:10 +02:00
Picnic-Bot
c58dceb9df Upgrade errorprone-slf4j 0.1.12 -> 0.1.13 (#53)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.13
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.12...v0.1.13
2022-08-07 14:21:02 +02:00
Picnic-Bot
90ef2f4042 Upgrade pitest-maven-plugin 1.9.3 -> 1.9.4 (#178)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.4
- https://github.com/hcoles/pitest/compare/1.9.3...1.9.4
2022-08-06 10:17:26 +02:00
Picnic-Bot
459a498d6c Upgrade maven-site-plugin 3.12.0 -> 3.12.1 (#177)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MSITE%20AND%20fixVersion%20%3E%203.12.0%20AND%20fixVersion%20%3C%3D%203.12.1%20AND%20statusCategory%20%3D%20Done%20
- https://github.com/apache/maven-site-plugin/compare/maven-site-plugin-3.12.0...maven-site-plugin-3.12.1
2022-08-06 09:39:38 +02:00
Picnic-Bot
78035644dc Upgrade NullAway 0.9.8 -> 0.9.9 (#175)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.8...v0.9.9
2022-08-05 09:10:49 +02:00
Picnic-Bot
ef67d41512 Upgrade pitest-maven-plugin 1.9.2 -> 1.9.3 (#159)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.3
- https://github.com/hcoles/pitest/compare/1.9.2...1.9.3
2022-08-04 16:53:54 +02:00
Picnic-Bot
4cecff923a Upgrade Checker Framework Annotations 3.23.0 -> 3.24.0 (#174)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.24.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.23.0...checker-framework-3.24.0
2022-08-04 08:10:10 +02:00
Stephan Schroevers
38a57db994 Introduce ErrorProneTestHelperSourceFormat check (#147)
This new checker inspects inline code passed to `CompilationTestHelper` and
`BugCheckerRefactoringTestHelper` instances. It requires that this code is
properly formatted and that its imports are organized. Only code that
represents the expected output of a refactoring operation is allowed to have
unused imports, as most `BugChecker`s do not (and are not able to) remove
imports that become obsolete as a result of applying their suggested fix(es).
2022-08-03 21:47:31 +02:00
Stephan Schroevers
336557cf8e Introduce apply-error-prone-suggestions.sh (#171)
This script compiles the code using Error Prone and applies its suggestions. 
The set of checks applied can optionally be restricted by name.
2022-08-03 17:05:38 +02:00
Picnic-Bot
9055dfff19 Upgrade Checkstyle 10.3.1 -> 10.3.2 (#173)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.2
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.1...checkstyle-10.3.2
2022-08-02 12:26:40 +02:00
Rick Ossendrijver
3712a15195 Implement workaround for MCOMPILER-503 (#149)
By moving around some annotation processor classpath entries we ensure that the
`maven-compiler-plugin` uses the appropriate version of Error Prone.

See https://issues.apache.org/jira/browse/MCOMPILER-503
2022-08-02 09:36:46 +02:00
Picnic-Bot
9d487e4a88 Upgrade New Relic Java Agent 7.8.0 -> 7.9.0 (#168)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.9.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.8.0...v7.9.0
2022-08-01 17:35:58 +02:00
Picnic-Bot
b2b086761c Upgrade JUnit Jupiter 5.8.2 -> 5.9.0 (#167)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.9.0
- https://github.com/junit-team/junit5/compare/r5.8.2...r5.9.0
2022-08-01 17:27:08 +02:00
Picnic-Bot
ff64247b6d Upgrade maven-resources-plugin 3.2.0 -> 3.3.0 (#166)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MRESOURCES%20AND%20fixVersion%20%3E%203.2.0%20AND%20fixVersion%20%3C%3D%203.3.0
- https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.2.0...maven-resources-plugin-3.3.0
2022-08-01 17:15:19 +02:00
Picnic-Bot
bc7443c72d Upgrade maven-install-plugin 3.0.0 -> 3.0.1 (#164)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MINSTALL%20AND%20fixVersion%20%3E%20%203.0.0%20AND%20fixVersion%20%3C%3D%203.0.1
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.0.0...maven-install-plugin-3.0.1
2022-08-01 16:21:54 +02:00
Picnic-Bot
abf4d68fba Upgrade extra-enforcer-rules 1.6.0 -> 1.6.1 (#165)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.6.1
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.6.0...extra-enforcer-rules-1.6.1
2022-08-01 16:20:24 +02:00
Picnic-Bot
5c5f7d849e Upgrade Guava 31.0.1-jre -> 31.1-jre (#77)
See:
- https://guava.dev/releases/31.1-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v31.1
- https://github.com/google/guava/compare/v31.0.1...v31.1
2022-08-01 14:48:56 +02:00
Picnic-Bot
65c4694936 Upgrade Error Prone 2.10.0 -> 2.14.0 (#76)
This also requires the upgrade of errorprone-slf4j 0.1.4 -> 0.1.12.

See:
- https://github.com/google/error-prone/releases/tag/v2.11.0
- https://github.com/google/error-prone/releases/tag/v2.12.0
- https://github.com/google/error-prone/releases/tag/v2.12.1
- https://github.com/google/error-prone/releases/tag/v2.13.0
- https://github.com/google/error-prone/releases/tag/v2.13.1
- https://github.com/google/error-prone/releases/tag/v2.14.0
- https://github.com/google/error-prone/compare/v2.10.0...v2.14.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.10.0-picnic-3...v2.14.0-picnic-2
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.5
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.6
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.7
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.8
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.9
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.10
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.11
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.12
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.4...v0.1.12
2022-07-31 15:50:16 +02:00
Picnic-Bot
a45291c7d8 Upgrade Spring Boot 2.7.1 -> 2.7.2 (#163)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.2
- https://github.com/spring-projects/spring-boot/compare/v2.7.1...v2.7.2
2022-07-30 15:11:37 +02:00
Picnic-Bot
71012f31ab Upgrade swagger-annotations 2.2.1 -> 2.2.2 (#162)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.2
- https://github.com/swagger-api/swagger-core/compare/v2.2.1...v2.2.2
2022-07-30 14:43:54 +02:00
Picnic-Bot
6e0905c033 Upgrade maven-install-plugin 2.5.2 -> 3.0.0 (#161)
See: 
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MINSTALL%20AND%20fixVersion%20%3E%20%202.5.2%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-2.5.2...maven-install-plugin-3.0.0
2022-07-21 09:01:43 +02:00
Picnic-Bot
af5ac85428 Upgrade maven-deploy-plugin 2.8.2 -> 3.0.0 (#160)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEPLOY%20AND%20fixVersion%20%3E%202.8.2%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-2.8.2...maven-deploy-plugin-3.0.0
2022-07-21 08:49:42 +02:00
Picnic-Bot
0329c25f78 Upgrade sortpom-maven-plugin 3.1.3 -> 3.2.0 (#157)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.2.0
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.1.3...sortpom-parent-3.2.0
2022-07-21 08:04:22 +02:00
Picnic-Bot
9e67e2b795 Upgrade Spring 5.3.21 -> 5.3.22 (#155)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.22
- https://github.com/spring-projects/spring-framework/compare/v5.3.21...v5.3.22
2022-07-15 14:52:27 +02:00
Picnic-Bot
4bafea05f4 Upgrade Project Reactor 2020.0.20 -> 2020.0.21 (#154)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.21
- https://github.com/reactor/reactor/compare/2020.0.20...2020.0.21
2022-07-15 08:46:21 +02:00
Picnic-Bot
8ce9cab2dd Upgrade Checker Framework Annotations 3.22.2 -> 3.23.0 (#153)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.23.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.22.2...checker-framework-3.23.0
2022-07-13 07:34:25 +02:00
Pieter Dirk Soels
ae30625524 Clarify MapToNullable Refaster template (#150) 2022-07-12 12:54:22 +02:00
Picnic-Bot
dc0046ebfc Upgrade extra-enforcer-rules 1.5.1 -> 1.6.0 (#152)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.6.0
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.5.1...extra-enforcer-rules-1.6.0
2022-07-12 11:41:23 +02:00
Picnic-Bot
ad6d774818 Upgrade pitest-maven-plugin 1.9.0 -> 1.9.2 (#151)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.1
- https://github.com/hcoles/pitest/releases/tag/1.9.2
- https://github.com/hcoles/pitest/compare/1.9.0...1.9.2
2022-07-12 11:11:30 +02:00
Stephan Schroevers
405f5874ac Apply Checkstyle to Refaster test resources (#146)
Similar to formatting the `*TemplatesTest{In,Out}put.java` files to enforce
certain style aspects, this is expected to reduce future code review effort.

The test code changes were applied to appease the
`SimplifyBooleanExpression` check.
2022-07-06 13:39:59 +02:00
Stephan Schroevers
bf5199ea3d Fix build-time Refaster template loading (#121)
When using the system classloader, `RefasterCheckTest` is able to
successfully load the Refaster templates from the classpath, causing the
tests to pass. However, when during a build the Java compiler loads
`RefasterCheck`, the templates on the annotation processor classpath are
_not_ exposed through the system classloader.
2022-06-30 13:30:03 +02:00
Gijs de Jong
c500516bb4 Prefer AssertJ's isNull() assertion over isEqualTo(null) (#133) 2022-06-29 18:55:22 +02:00
Picnic-Bot
85d68a4f34 Upgrade Spring Boot 2.5.14 -> 2.7.1 (#81)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.0-M1
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.0-M2
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.0-M3
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.0-RC1
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.0
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.1
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.2
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.3
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.4
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.5
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.6
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.7
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.8
- https://github.com/spring-projects/spring-boot/releases/tag/v2.6.9
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.0-M1
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.0-M2
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.0-M3
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.0-RC1
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.0
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.1
- https://github.com/spring-projects/spring-boot/compare/v2.5.14...v2.7.1
2022-06-29 10:33:15 +02:00
Rick Ossendrijver
46467951dd Have Checkstyle disallow blank lines at the start of blocks (#141) 2022-06-28 14:11:55 +02:00
Picnic-Bot
6f7ce2067f Upgrade Checkstyle 10.3 -> 10.3.1 (#148)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.1
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3...checkstyle-10.3.1
2022-06-28 12:58:32 +02:00
Gijs de Jong
f30ba36d18 Prefer {Mono,Flux}#cast over {Mono,Flux}#map performing a cast (#127) 2022-06-28 08:51:26 +02:00
Picnic-Bot
65736ce83f Upgrade NullAway 0.9.7 -> 0.9.8 (#145)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.7...v0.9.8
2022-06-27 08:19:39 +02:00
Rick Ossendrijver
17d5805d5a Drop public modifier from BugPattern test classes (#136) 2022-06-26 11:20:07 +02:00
Picnic-Bot
ce01b62832 Upgrade pitest-maven-plugin 1.8.1 -> 1.9.0 and pitest-junit5-plugin 0.16 -> 1.0.0 (#143)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.0
- https://github.com/hcoles/pitest/compare/1.8.1...1.9.0
- https://github.com/pitest/pitest-junit5-plugin/compare/0.16...1.0.0
2022-06-24 14:51:13 +02:00
Picnic-Bot
4ee555c62b Upgrade maven-enforcer-plugin 3.0.0 -> 3.1.0 (#126)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MENFORCER%20AND%20fixVersion%20%3E%203.0.0%20AND%20fixVersion%20%3C%3D%203.1.0%20
- https://github.com/apache/maven-enforcer/compare/enforcer-3.0.0...enforcer-3.1.0
2022-06-21 13:23:32 +02:00
Gijs de Jong
a24bbbe99d Rewrite Web{,Test}Client HTTP method specification expressions (#129) 2022-06-21 07:37:04 +02:00
Gijs de Jong
679e83bc48 Replace unnecessary UriBuilder arguments passed to Web(Test)Client#uri (#128) 2022-06-20 22:37:49 +02:00
Gijs de Jong
0bdc171613 Introduce CollectorMutability check (#135)
This check flags `Collectors.to{List,Map,Set}` usages, suggesting that they be
replaced with expressions that clearly indicate the (im)mutability of the 
resultant collection.

Refaster templates that violated the new check are simplified by removing the
relevant expressions; they were too contrived to be updated some other way.
2022-06-20 11:08:06 +02:00
Picnic-Bot
9b05ec62c6 Upgrade Spring Boot 2.5.6 -> 2.5.14 (#134)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.7
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.8
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.9
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.10
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.11
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.12
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.13
- https://github.com/spring-projects/spring-boot/releases/tag/v2.5.14
- https://github.com/spring-projects/spring-boot/compare/v2.5.6...v2.5.14
2022-06-20 09:52:31 +02:00
Picnic-Bot
72866183f5 Upgrade Spring 5.3.20 -> 5.3.21 (#139)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.21
- https://github.com/spring-projects/spring-framework/compare/v5.3.20...v5.3.21
2022-06-20 09:08:43 +02:00
Picnic-Bot
9d7f569be5 Upgrade swagger-annotations 2.2.0 -> 2.2.1 (#137)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.1
- https://github.com/swagger-api/swagger-core/compare/v2.2.0...v2.2.1
2022-06-19 17:59:34 +02:00
Picnic-Bot
f72adca292 Upgrade New Relic Java Agent 7.7.0 -> 7.8.0 (#140)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.8.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.7.0...v7.8.0
2022-06-19 15:35:04 +02:00
Picnic-Bot
5148143ae5 Upgrade Project Reactor 2020.0.19 -> 2020.0.20 (#130)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.20
- https://github.com/reactor/reactor/compare/2020.0.19...2020.0.20
2022-06-19 15:07:38 +02:00
Picnic-Bot
3d7fbbf7a2 Upgrade pitest-maven-plugin 1.8.0 -> 1.8.1 (#138)
See:
- https://github.com/hcoles/pitest/releases/tag/1.8.1
- https://github.com/hcoles/pitest/compare/1.8.0...1.8.1
2022-06-19 14:57:43 +02:00
Picnic-Bot
8b6864d8a0 Upgrade baseline-error-prone 4.42.0 -> 4.145.0 (#58)
See:
- https://github.com/palantir/gradle-baseline/releases/tag/4.47.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.48.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.49.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.50.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.51.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.52.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.53.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.54.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.55.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.56.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.57.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.58.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.59.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.60.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.61.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.62.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.63.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.64.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.65.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.66.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.67.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.68.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.69.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.70.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.71.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.72.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.73.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.74.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.75.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.76.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.77.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.78.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.79.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.80.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.81.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.82.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.83.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.84.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.85.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.86.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.87.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.88.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.89.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.90.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.91.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.92.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.93.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.94.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.95.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.96.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.97.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.98.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.99.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.100.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.101.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.102.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.103.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.104.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.105.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.106.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.107.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.108.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.109.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.110.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.111.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.112.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.113.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.114.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.115.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.116.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.117.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.118.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.119.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.120.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.121.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.122.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.123.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.124.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.125.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.126.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.127.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.128.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.129.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.130.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.131.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.132.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.133.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.134.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.135.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.136.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.137.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.138.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.139.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.140.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.141.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.142.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.143.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.144.0
- https://github.com/palantir/gradle-baseline/releases/tag/4.145.0
- https://github.com/palantir/gradle-baseline/compare/4.46.0...4.145.0
2022-06-19 14:40:46 +02:00
Picnic-Bot
a03017b5e6 Upgrade Checker Framework Annotations 3.22.1 -> 3.22.2 (#131)
See https://github.com/typetools/checker-framework/compare/checker-framework-3.22.1...checker-framework-3.22.2
2022-06-16 22:23:05 +02:00
Rick Ossendrijver
a227436f19 Introduce .renovaterc.json (#125) 2022-06-15 16:46:12 +02:00
Picnic-Bot
3f6558b7c0 Upgrade fmt-maven-plugin 2.18 -> 2.19 (#132)
See:
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.19.0
- https://github.com/spotify/fmt-maven-plugin/compare/2.18.0...2.19.0
2022-06-15 08:43:35 +02:00
Picnic-Bot
ce06396521 Upgrade pitest-junit5-plugin 0.15 -> 0.16 (#124)
See https://github.com/pitest/pitest-junit5-plugin/compare/0.15...0.16
2022-06-11 14:39:01 +02:00
Rick Ossendrijver
268766a32a Fix copy-paste error in ReactorTemplates Javadoc (#123) 2022-06-09 09:08:36 +02:00
Rick Ossendrijver
5366effd74 Upgrade Error Prone fork v2.10.0-picnic-1 -> v2.10.0-picnic-3 (#120)
See:
- https://github.com/PicnicSupermarket/error-prone/releases/tag/v2.10.0-picnic-2
- https://github.com/PicnicSupermarket/error-prone/releases/tag/v2.10.0-picnic-3
- https://github.com/PicnicSupermarket/error-prone/compare/v2.10.0-picnic-1...v2.10.0-picnic-3
2022-06-08 12:55:40 +02:00
Picnic-Bot
0a63361500 Upgrade Truth 1.0.1 -> 1.1.3 (#78)
See:
- https://github.com/google/truth/releases/tag/release_1_1
- https://github.com/google/truth/releases/tag/release_1_1_1
- https://github.com/google/truth/releases/tag/release_1_1_2
- https://github.com/google/truth/releases/tag/release_1_1_3
- https://github.com/google/truth/compare/release_1_0_1...release_1_1_3
2022-06-05 20:52:45 +02:00
Picnic-Bot
a074e2fdd6 Upgrade Checker Framework Annotations 3.22.0 -> 3.22.1 (#117)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.22.1
- https://github.com/typetools/checker-framework/compare/checker-framework-3.22.0...checker-framework-3.22.1
2022-06-03 14:59:14 +02:00
Picnic-Bot
f06d4e41cf Upgrade Mockito 4.6.0 -> 4.6.1 (#118)
See:
- https://github.com/mockito/mockito/releases/tag/v4.6.1
- https://github.com/mockito/mockito/compare/v4.6.0...v4.6.1
2022-06-03 09:20:27 +02:00
Rick Ossendrijver
cf7bb657fa Enable Checkstyle's LocalVariableName check (#111) 2022-06-01 16:53:07 +02:00
Rick Ossendrijver
8277b43955 Move Refaster template test resources to proper package (#113) 2022-06-01 14:55:29 +02:00
Picnic-Bot
691f2c8311 Upgrade AssertJ Core 3.22.0 -> 3.23.1 (#116)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.22.0...assertj-core-3.23.1
2022-06-01 13:09:17 +02:00
Rick Ossendrijver
362518b0f4 Resolve Refaster template naming and test inconsistencies (#109) 2022-05-31 13:54:17 +02:00
Picnic-Bot
b9a3840e25 Upgrade Checkstyle 10.2 -> 10.3 (#115)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.2...checkstyle-10.3
2022-05-31 10:40:39 +02:00
Picnic-Bot
14d8bddbec Upgrade sortpom-maven-plugin 3.0.1 -> 3.1.3 (#112)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.1.0
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.1.1
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.1.2
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.1.3
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.0.1...sortpom-parent-3.1.3
2022-05-31 09:51:13 +02:00
Picnic-Bot
6c7a0b81ea Upgrade Mockito 4.5.1 -> 4.6.0 (#114)
See:
- https://github.com/mockito/mockito/releases/tag/v4.6.0
- https://github.com/mockito/mockito/compare/v4.5.1...v4.6.0
2022-05-31 09:35:59 +02:00
David Mischke
3a3825f7ba Simplify unnecessary String.format(...) expressions passed to AssertJ (#101) 2022-05-26 14:24:23 +02:00
Stephan Schroevers
31e54cc990 Enforce test resources code formatting (#105)
This change should have been part of #75.
2022-05-26 13:56:19 +02:00
Pim Tegelaar
775a2688ca Introduce AssertJThrowingCallableTemplates (#92) 2022-05-26 13:29:35 +02:00
Rick Ossendrijver
3d854a0cc5 Rename RefasterRuleResourceCompiler to RefasterRuleCompiler (#110)
The associated Maven module is renamed from `refaster-resource-compiler` to
just `refaster-compiler`.
2022-05-26 13:21:28 +02:00
Stephan Schroevers
6ee013da58 Drop unnecessary dependency declarations (#106) 2022-05-26 10:51:47 +02:00
David Mischke
962d18dcb5 Drop unnecessary AssertJ usage in StepVerifier expression (#103) 2022-05-23 22:55:09 +02:00
Picnic-Bot
aab308a190 Upgrade pitest-maven-plugin 1.7.6 -> 1.8.0 (#108)
See:
- https://github.com/hcoles/pitest/releases/tag/1.8.0
- https://github.com/hcoles/pitest/compare/1.7.6...1.8.0
2022-05-23 09:21:53 +02:00
Picnic-Bot
009bd5d0d7 Upgrade versions-maven-plugin 2.10.0 -> 2.11.0 (#107)
See:
- https://github.com/mojohaus/versions-maven-plugin/releases/tag/versions-maven-plugin-2.11.0
- https://github.com/mojohaus/versions-maven-plugin/compare/versions-maven-plugin-2.10.0...versions-maven-plugin-2.11.0
2022-05-23 08:45:15 +02:00
Pim Tegelaar
8e57f64e31 Prevent JUnitMethodDeclarationCheck from renaming likely-overridden methods (#93) 2022-05-17 18:15:31 +02:00
Picnic-Bot
53e81f3611 Upgrade Jackson 2.13.2.20220328 -> 2.13.3 (#100)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.13.3
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.2.20220328...jackson-bom-2.13.3
2022-05-17 11:41:56 +02:00
Picnic-Bot
8745105251 Upgrade Spring 5.3.19 -> 5.3.20 (#99)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.20
- https://github.com/spring-projects/spring-framework/compare/v5.3.19...v5.3.20
2022-05-17 11:24:18 +02:00
Picnic-Bot
3eb3c20b1d Upgrade Project Reactor 2020.0.18 -> 2020.0.19 (#98)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.19
- https://github.com/reactor/reactor/compare/2020.0.18...2020.0.19
2022-05-17 10:26:33 +02:00
Picnic-Bot
121618f277 Upgrade NullAway 0.9.6 -> 0.9.7 (#97)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.6...v0.9.7
2022-05-17 10:09:59 +02:00
Picnic-Bot
5cf4194168 Upgrade pitest-maven-plugin 1.7.5 -> 1.7.6 (#94)
See:
- https://github.com/hcoles/pitest/releases/tag/1.7.6
- https://github.com/hcoles/pitest/compare/1.7.5...1.7.6
2022-05-10 09:28:27 +02:00
Picnic-Bot
c434cd318c Upgrade New Relic Java Agent 7.6.0 -> 7.7.0
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.7.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.6.0...v7.7.0
2022-05-10 09:02:30 +02:00
Picnic-Bot
22aa9cb213 Upgrade Checker Framework Annotations 3.21.4 -> 3.22.0
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.22.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.21.4...checker-framework-3.22.0
2022-05-10 07:36:16 +02:00
Rick Ossendrijver
3edc483e7c Extend set of parameter types recognized by RequestMappingAnnotationCheck (#91)
While there, restrict the supported set of `WebRequest` subtypes to just
`WebRequest` and `NativeWebRequest`.
2022-04-29 14:15:51 +02:00
Stephan Schroevers
fff5d7b329 Apply assorted Maven parent improvements (#85) 2022-04-29 13:35:14 +02:00
Picnic-Bot
4ca80952ab Upgrade modernizer-maven-plugin 2.3.0 -> 2.4.0 (#90)
See:
- https://github.com/gaul/modernizer-maven-plugin/releases/tag/modernizer-maven-plugin-2.4.0
- https://github.com/gaul/modernizer-maven-plugin/compare/modernizer-maven-plugin-2.3.0...modernizer-maven-plugin-2.4.0
2022-04-26 12:52:11 +02:00
Picnic-Bot
8cecb8bf30 Upgrade Spring 5.3.13 -> 5.3.19 (#74)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.14
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.15
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.16
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.17
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.18
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.19
- https://github.com/spring-projects/spring-framework/compare/v5.3.13...v5.3.19
2022-04-26 12:38:23 +02:00
Picnic-Bot
a937bf0ddf Upgrade Checker Framework Annotations 3.19.0 -> 3.21.4 (#87)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.20.0
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.21.0
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.21.1
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.21.2
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.21.3
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.21.4
- https://github.com/typetools/checker-framework/compare/checker-framework-3.19.0...checker-framework-3.21.4
2022-04-26 12:31:02 +02:00
Picnic-Bot
0b71c2b576 Upgrade versions-maven-plugin 2.8.1 -> 2.10.0 (#89)
See:
- https://github.com/mojohaus/versions-maven-plugin/releases/tag/versions-maven-plugin-2.9.0
- https://github.com/mojohaus/versions-maven-plugin/releases/tag/versions-maven-plugin-2.10.0
- https://github.com/mojohaus/versions-maven-plugin/compare/versions-maven-plugin-2.8.1...versions-maven-plugin-2.10.0
2022-04-26 11:35:51 +02:00
Picnic-Bot
239d38d69f Upgrade extra-enforcer-rules 1.4 -> 1.5.1 (#88)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.5.0
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.5.1
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.4...extra-enforcer-rules-1.5.1
2022-04-26 11:09:17 +02:00
Picnic-Bot
ab57aa5eb6 Upgrade pitest-maven-plugin 1.7.3 -> 1.7.5 (#72)
See:
- https://github.com/hcoles/pitest/releases/tag/1.7.4
- https://github.com/hcoles/pitest/releases/tag/1.7.5
- https://github.com/hcoles/pitest/compare/1.7.3...1.7.5
2022-04-25 22:46:44 +02:00
Picnic-Bot
49267337cb Upgrade JUnit Jupiter 5.8.1 -> 5.8.2 (#71)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.8.2
- https://github.com/junit-team/junit5/compare/r5.8.1...r5.8.2
2022-04-25 13:56:02 +02:00
Picnic-Bot
c201fe1fd2 Upgrade SLF4J API 1.7.32 -> 1.7.36 (#73)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_1.7.32...v_1.7.36
2022-04-25 13:48:50 +02:00
Picnic-Bot
fb20b6f93d Upgrade AutoValue 1.8.2 -> 1.9 (#83)
See:
- https://github.com/google/auto/releases/tag/auto-value-1.9
- https://github.com/google/auto/compare/auto-value-1.8.2...auto-value-1.9
2022-04-25 13:36:41 +02:00
Picnic-Bot
9264c25b57 Upgrade fmt-maven-plugin 2.12 -> 2.18 (#75)
This also upgrades Google Java Format 1.11.0 -> 1.15.0, improving support for 
Java 17 syntax, among other things.

See:
- https://github.com/google/google-java-format/releases/tag/v1.12.0
- https://github.com/google/google-java-format/releases/tag/v1.13.0
- https://github.com/google/google-java-format/releases/tag/v1.14.0
- https://github.com/google/google-java-format/releases/tag/v1.15.0
- https://github.com/google/google-java-format/compare/v1.11.0...v1.15.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.13.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.14.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.15.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.16.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.17.0
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.18.0
- https://github.com/spotify/fmt-maven-plugin/compare/2.12.0...2.18.0
2022-04-25 13:29:19 +02:00
Picnic-Bot
ca628eef6c Upgrade New Relic Java Agent 7.4.3 -> 7.6.0 (#79)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.5.0
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.6.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.4.3...v7.6.0
2022-04-25 09:27:25 +02:00
Picnic-Bot
62168dcd4b Upgrade Checkstyle 10.1 -> 10.2 (#80)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.2
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.1...checkstyle-10.2
2022-04-25 09:17:24 +02:00
Picnic-Bot
96a82eaf85 Upgrade Mockito 4.0.0 -> 4.5.1 (#84)
See:
- https://github.com/mockito/mockito/releases/tag/v4.1.0
- https://github.com/mockito/mockito/releases/tag/v4.2.0
- https://github.com/mockito/mockito/releases/tag/v4.3.0
- https://github.com/mockito/mockito/releases/tag/v4.3.1
- https://github.com/mockito/mockito/releases/tag/v4.4.0
- https://github.com/mockito/mockito/releases/tag/v4.5.0
- https://github.com/mockito/mockito/releases/tag/v4.5.1
- https://github.com/mockito/mockito/compare/v4.0.0...v4.5.1
2022-04-25 09:06:44 +02:00
Picnic-Bot
0c615b4c15 Upgrade Project Reactor 2020.0.13 -> 2020.0.18 (#51)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.14
- https://github.com/reactor/reactor/releases/tag/2020.0.15
- https://github.com/reactor/reactor/releases/tag/2020.0.16
- https://github.com/reactor/reactor/releases/tag/2020.0.17
- https://github.com/reactor/reactor/releases/tag/2020.0.18
- https://github.com/reactor/reactor/compare/2020.0.13...2020.0.18
2022-04-24 17:05:53 +02:00
Picnic-Bot
6304130ae3 Upgrade swagger-annotations 1.6.3 -> 1.6.6 (#52)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.4
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.5
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.6
- https://github.com/swagger-api/swagger-core/compare/v1.6.3...v1.6.6
2022-04-24 16:57:18 +02:00
Picnic-Bot
1674e99ba4 Upgrade maven-jar-plugin 3.2.0 -> 3.2.2 (#54)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MJAR%20AND%20fixVersion%20%3E%203.2.0%20AND%20fixVersion%20%3C%3D%203.2.2
- https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.2.0...maven-jar-plugin-3.2.2
2022-04-24 16:49:47 +02:00
Picnic-Bot
75bbc8e5b5 Upgrade AspectJ 1.9.7 -> 1.9.9.1 (#55)
See:
- https://github.com/eclipse/org.aspectj/releases/tag/V1_9_8
- https://github.com/eclipse/org.aspectj/releases/tag/V1_9_9
- https://github.com/eclipse/org.aspectj/releases/tag/V1_9_9_1
- https://github.com/eclipse/org.aspectj/compare/V1_9_7...V1_9_9_1
2022-04-24 16:27:16 +02:00
Picnic-Bot
fe3ed2be63 Upgrade jacoco-maven-plugin 0.8.7 -> 0.8.8 (#56)
See:
- https://github.com/jacoco/jacoco/releases/tag/v0.8.8
- https://github.com/jacoco/jacoco/compare/v0.8.7...v0.8.8
2022-04-24 16:20:18 +02:00
Picnic-Bot
f36350f3ba Upgrade Forbidden APIs plugin 3.2 -> 3.3 (#59)
See https://github.com/policeman-tools/forbidden-apis/compare/3.2...3.3
2022-04-24 16:12:47 +02:00
Picnic-Bot
4888ad3aaf Upgrade swagger-annotations 2.1.11 -> 2.2.0 (#60)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.1.12
- https://github.com/swagger-api/swagger-core/releases/tag/v2.1.13
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.0
- https://github.com/swagger-api/swagger-core/compare/v2.1.11...v2.2.0
2022-04-24 16:04:48 +02:00
Picnic-Bot
9daf73f148 Upgrade maven-clean-plugin 3.1.0 -> 3.2.0 (#61)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCLEAN%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.2.0
- https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.1.0...maven-clean-plugin-3.2.0
2022-04-24 15:55:59 +02:00
Picnic-Bot
b08ca514c1 Upgrade maven-compiler-plugin 3.8.1 -> 3.10.1 (#62)
And drop the redundant `--source` and `--target` flags.

See:
- https://github.com/apache/maven-compiler-plugin/releases/tag/maven-compiler-plugin-3.9.0
- https://github.com/apache/maven-compiler-plugin/releases/tag/maven-compiler-plugin-3.10.0
- https://github.com/apache/maven-compiler-plugin/releases/tag/maven-compiler-plugin-3.10.1
- https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.1...maven-compiler-plugin-3.10.1
2022-04-24 15:48:25 +02:00
Picnic-Bot
4eed7dd0e8 Upgrade maven-dependency-plugin 3.1.2 -> 3.3.0 (#63)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEP%20AND%20fixVersion%20%3E%203.1.2%20AND%20fixVersion%20%3C%3D%203.3.0
- https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.1.2...maven-dependency-plugin-3.3.0
2022-04-24 15:37:56 +02:00
Picnic-Bot
618c62e7a5 Upgrade maven-javadoc-plugin 3.3.1 -> 3.4.0 (#64)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MJAVADOC%20AND%20fixVersion%20%3E%203.3.1%20AND%20fixVersion%20%3C%3D%203.4.0
- https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.1...maven-javadoc-plugin-3.4.0
2022-04-24 15:30:33 +02:00
Picnic-Bot
b46f075ddf Upgrade maven-site-plugin 3.9.1 -> 3.12.0 (#65)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MSITE%20AND%20fixVersion%20%3E%203.9.1%20AND%20fixVersion%20%3C%3D%203.12.0
- https://github.com/apache/maven-site-plugin/compare/maven-site-plugin-3.9.1...maven-site-plugin-3.12.0
2022-04-24 15:12:26 +02:00
Picnic-Bot
64436ff9b6 Upgrade AssertJ Core 3.21.0 -> 3.22.0 (#66)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.21.0...assertj-core-3.22.0
2022-04-24 15:03:13 +02:00
Picnic-Bot
e028077f1e Upgrade Checkstyle 9.1 -> 10.1 (#67)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-9.2
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-9.2.1
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-9.3
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.0
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.1
- https://github.com/checkstyle/checkstyle/compare/checkstyle-9.1...checkstyle-10.1
2022-04-24 14:51:37 +02:00
Picnic-Bot
5aa386b5ba Upgrade buildnumber-maven-plugin 1.4 -> 3.0.0 (#69)
See:
- https://github.com/mojohaus/buildnumber-maven-plugin/releases/tag/buildnumber-maven-plugin-3.0.0
- https://github.com/mojohaus/buildnumber-maven-plugin/compare/buildnumber-maven-plugin-1.4...buildnumber-maven-plugin-3.0.0
2022-04-24 14:44:35 +02:00
Picnic-Bot
a480fe908d Upgrade Palantir assertj-error-prone 0.3.0 -> 0.6.0 (#57)
See:
- https://github.com/palantir/assertj-automation/releases/tag/0.4.0
- https://github.com/palantir/assertj-automation/releases/tag/0.5.0
- https://github.com/palantir/assertj-automation/releases/tag/0.6.0
- https://github.com/palantir/assertj-automation/compare/0.3.0...0.6.0
2022-04-24 14:37:29 +02:00
Picnic-Bot
b2316c744c Upgrade Hamcrest 1.3 -> 2.2 (#70)
See:
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.1-rc1
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.1-rc2
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.1-rc3
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.1-rc4
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.1
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.2-rc1
- https://github.com/hamcrest/JavaHamcrest/releases/tag/v2.2
- https://github.com/hamcrest/JavaHamcrest/compare/hamcrest-java-1.3...v2.2
2022-04-24 14:24:54 +02:00
Picnic-Bot
9334babfe8 Upgrade sortpom-maven-plugin 3.0.0 -> 3.0.1 (#48)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.0.1
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.0.0...sortpom-parent-3.0.1
2022-04-24 14:15:13 +02:00
Picnic-Bot
b6632d393b Upgrade AutoCommon 1.2 -> 1.2.1 (#49)
See:
- https://github.com/google/auto/releases/tag/auto-common-1.2.1
- https://github.com/google/auto/compare/auto-common-1.2...auto-common-1.2.1
2022-04-24 14:01:28 +02:00
Picnic-Bot
53d191ff4f Upgrade NullAway 0.9.2 -> 0.9.6 (#50)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.2...v0.9.6
2022-04-24 13:19:49 +02:00
Picnic-Bot
b8ddf3ac20 Upgrade Jackson 2.13.0 -> 2.13.2.20220328 (#47)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.13.1
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.13.2
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.0...jackson-bom-2.13.2.20220328
2022-04-24 13:05:17 +02:00
Rick Ossendrijver
e859c2774f JUnitMethodDeclarationCheck: better handle possibly-problematic method renames (#35)
By flagging affected test methods, but not suggesting a specific rename.
2022-04-12 18:16:21 +02:00
Rick Ossendrijver
1cb4ce97ae Require static import of most java.util.Collections methods (#23)
While there, statically import `ZoneOffset.UTC` in a few places.
2022-04-12 17:48:53 +02:00
Pieter Dirk Soels
92fe96286f Prefer Collectors.joining() over Collectors.joining("") (#44) 2022-04-12 14:25:39 +02:00
Vitor Mussatto
d7531abceb Introduce additional Immutable{List,Map,Set}#of Refaster templates (#40) 2022-04-12 11:51:45 +02:00
João Nogueira
cc7074a62f Introduce RequestParamTypeCheck (#33) 2022-04-12 10:45:34 +02:00
Pieter Dirk Soels
91922454b6 Require static import of com.google.errorprone.refaster.ImportPolicy members (#45) 2022-04-12 10:36:44 +02:00
Rick Ossendrijver
eaa98c985c Introduce assorted AssertJ Refaster templates (#37) 2022-04-11 22:25:14 +02:00
Pieter Dirk Soels
94908d6a37 Require static import of java.util.UUID#randomUUID (#42) 2022-04-11 17:55:57 +02:00
Rick Ossendrijver
e8d9221424 Have PrimitiveComparisonCheck retain relevant generic type parameters (#39) 2022-04-11 17:13:06 +02:00
Rick Ossendrijver
fea8f5b2c0 Suggest assorted canonical ZoneOffset usages (#38)
- Statically import `ZoneOffset.UTC`.
- Prefer `Instant#atOffset` over `OffsetDateTime#ofInstant`.
2022-04-11 10:16:37 +02:00
Rick Ossendrijver
2199accedc Drop or rewrite AssertJBigDecimalTemplates rules (#30) 2022-04-11 10:08:15 +02:00
Rick Ossendrijver
83670aeddf Require static import of java.util.regex.Pattern constants (#36)
The meaning of these constants will be apparent from context.

Note that `Pattern.{compile,matches,quote}` should not be statically imported,
as these methods have rather generic names.
2022-04-11 09:19:40 +02:00
Rick Ossendrijver
e8977bea67 Require static import of BugPattern.{LinkType,ServerityLevel,StandardTags} members (#34) 2022-04-11 08:16:20 +02:00
Pieter Dirk Soels
0d051064bb Prefer Duration.ofX(n) over Duration.of(n, ChronoUnit.X) (#41)
`TimeTemplates` now contains Refaster templates for
`Duration.of{Nanos,Millis,Seconds,Minutes,Hours,Days}(n)`.
2022-04-10 16:02:33 +02:00
Rick Ossendrijver
06913b6a5b Drop obsolete README entry (#29)
This issue is was resolved by google/error-prone#2257.
2022-04-10 15:34:36 +02:00
Rick Ossendrijver
d2bbee3ed9 Introduce IdentityConversionCheck (#27)
And remove any Refaster templates it subsumes.
2022-04-08 13:28:12 +02:00
Rick Ossendrijver
793a70c29b Configure static import policy for AssertThatSetContainsExactlyOneElement Refaster template (#32) 2022-01-24 10:02:34 +01:00
Rick Ossendrijver
63b4fae185 Introduce FluxFlatMapUsageCheck (#26)
And two (semi-)related Refaster templates.
2022-01-15 12:39:34 +01:00
Stephan Schroevers
73f8f056b4 Introduce ScheduledTransactionTraceCheck (#31) 2022-01-10 10:21:20 +01:00
Rick Ossendrijver
c1638066cd Define assorted StaticImportCheck exemptions
While there, require that most `org.springframework.http.MediaType` members are
statically imported.
2022-01-03 10:20:46 +01:00
Rick Ossendrijver
08e99fb54e Use matching lambda parameter names in Refaster before- and after-templates (#24) 2022-01-01 14:04:37 +01:00
Rick Ossendrijver
00012c6aa8 Extend StaticImportCheck test coverage (#21) 2021-11-14 14:39:15 +01:00
Rick Ossendrijver
193589d193 Require static import of SpringBootTest.WebEnvironment constants (#19) 2021-11-14 14:26:17 +01:00
Stephan Schroevers
1a1588d413 Upgrade Error Prone 2.9.0 -> 2.10.0 2021-11-14 13:50:26 +01:00
Stephan Schroevers
6656bd8186 Assorted upgrades 2021-11-14 13:50:26 +01:00
Rick Ossendrijver
26a9b46de7 Add Refaster rules for StepVerifier creation (#18) 2021-11-14 13:48:39 +01:00
Phil Werli
17e74f778b Add Refaster rules for redundant Flux#concat invocations (#20) 2021-11-13 18:04:49 +01:00
Stephan Schroevers
e1bdb098de Upgrade AssertJ Core 3.20.2 -> 3.21.0
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.20.2...assertj-core-3.21.0
2021-09-25 13:53:58 +02:00
Stephan Schroevers
0be162c837 Assorted upgrades 2021-09-25 13:53:31 +02:00
Stephan Schroevers
edfb2a70e8 Upgrade Guava 30.1.1-jre -> 31.0-jre
And drop Refaster rules for APIs that are now `@Deprecated` or have an
`@InlineMe` annotation.

See:
- https://guava.dev/releases/31.0-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v31.0
- https://github.com/google/guava/compare/v30.1.1...v31.0
2021-09-25 13:17:10 +02:00
Stephan Schroevers
c1e5dc4339 Assorted upgrades 2021-09-19 21:07:40 +02:00
Stephan Schroevers
7218006b3c Assorted upgrades 2021-09-03 07:42:57 +02:00
Rick Ossendrijver
a3f599ba1b Introduce WebClientTemplates (#16) 2021-08-30 21:54:28 +02:00
Stephan Schroevers
f819a3e42f Assorted upgrades 2021-08-20 08:50:15 +02:00
Stephan Schroevers
7fc865c769 Light RefasterRuleResourceCompilerTaskListener cleanup 2021-08-19 22:43:10 +02:00
Stephan Schroevers
0287060d96 Fix and optimize FormatStringConcatenationCheck 2021-08-19 22:42:41 +02:00
Stephan Schroevers
ac4e81f2c7 Support running the build using JDK 16+ 2021-08-14 19:29:55 +02:00
Stephan Schroevers
350f028781 Sync with PSM parent 2021-08-14 19:13:31 +02:00
Stephan Schroevers
299ce7b800 Assorted upgrades 2021-08-14 19:13:31 +02:00
Stephan Schroevers
f3a929a3bb Upgrade fmt-maven-plugin, reformat 2021-08-04 10:59:23 +02:00
Stephan Schroevers
625a55add5 Add extra Refaster rule, document possible FormatStringConcatenation extension 2021-08-04 10:58:16 +02:00
Stephan Schroevers
ef59340987 Assorted upgrades 2021-08-04 10:58:05 +02:00
Stephan Schroevers
f2aea38b17 Introduce AbstractOptionalAssertContainsSame Refaster template 2021-08-03 13:53:44 +02:00
Stephan Schroevers
5cd8863eb5 Slightly optimize MethodReferenceUsage check 2021-08-01 19:10:46 +02:00
Stephan Schroevers
5b57b4720b Introduce MockitoStubbing check 2021-08-01 18:29:54 +02:00
Stephan Schroevers
5224571407 Assorted FormatStringConcatenationCheck improvements 2021-08-01 15:56:32 +02:00
Stephan Schroevers
c8189e9431 Document RefasterCheck follow-up task 2021-08-01 12:41:00 +02:00
Stephan Schroevers
0fe42f1ea6 Introduce FormatStringConcatenation check 2021-08-01 12:32:08 +02:00
Stephan Schroevers
78b29107a3 Upgrade dependencies 2021-07-29 12:09:19 +02:00
Stephan Schroevers
7ae7d5105a Document StreamMapFirst gotcha, courtesy of Nathan 2021-07-29 11:51:11 +02:00
Stephan Schroevers
0976b33d61 Support Refaster templates without an @AfterTemplate method 2021-07-29 11:48:51 +02:00
Stephan Schroevers
4b6f81c4fa imeZoneUsageCheck: dDon't flag likely overrides 2021-07-27 17:44:04 +02:00
Stephan Schroevers
1e879c175e Don't flag empty test (helper) methods 2021-07-27 17:43:08 +02:00
Stephan Schroevers
d014fed400 Deduplicate Refaster templates 2021-07-24 23:20:26 +02:00
Stephan Schroevers
1357f60fbc Introduce refaster-support module
This new module is meant to expose helper logic for use with Refaster
templates. For a start, one can now use e.g.
`@NotMatches(IsArray.class)`.
2021-07-24 23:20:25 +02:00
Stephan Schroevers
03abbbf99c Introduce RefasterAnyOfUsageCheck 2021-07-24 17:08:08 +02:00
Stephan Schroevers
9b845782c2 Cleanup and add package-info.java files 2021-07-24 16:55:46 +02:00
Stephan Schroevers
08ce33fb19 Upgrade, fix typo 2021-07-24 12:18:00 +02:00
Stephan Schroevers
182724bc8e Upgrade dependencies 2021-07-23 08:00:18 +02:00
Stephan Schroevers
e9d361713a Upgrade Error Prone, deduplicate version configuration 2021-07-23 07:55:47 +02:00
Stephan Schroevers
b754880556 Assorted upgrades 2021-07-18 14:59:30 +02:00
Stephan Schroevers
affd5c7b93 Support self-application of the checks 2021-07-18 14:59:30 +02:00
Stephan Schroevers
d86611e66a TimeZoneUsageCheck should not flag Instant.now(clock)
Users _should_ rewrite that to `clock.instant()`, and indeed we have a
Refaster check for that.
2021-07-18 11:04:36 +02:00
Stephan Schroevers
2d6100a679 Further nudge to AssertJ 2021-07-18 10:46:47 +02:00
Stephan Schroevers
912797a55a Bintray is no more; Palantir now deploys to Maven Central 2021-07-18 10:46:47 +02:00
Stephan Schroevers
d097f31124 Assorted upgrades 2021-06-26 10:45:33 +02:00
Stephan Schroevers
eb05582f79 Assorted upgrades 2021-06-19 16:33:24 +02:00
Stephan Schroevers
964fc35b71 Upgrades 2021-05-29 18:04:57 +02:00
Stephan Schroevers
ddc05bf88c Assorted upgrades 2021-05-15 13:07:54 +02:00
Stephan Schroevers
24afa6a755 Some improvements from oss-parent 2021-04-24 00:00:10 +02:00
Stephan Schroevers
8e97121bdf Upgrade dependency 2021-04-23 22:37:19 +02:00
Stephan Schroevers
567c81a93d Upgrade to Error Prone 2.6.0 2021-04-23 22:20:11 +02:00
Stephan Schroevers
c0bfac7b4c Upgrades, some syncing with other parent 2021-04-22 15:10:58 +02:00
Rick Ossendrijver
28138f35eb Assorted upgrades (#15) 2021-04-19 09:51:25 +02:00
Stephan Schroevers
a9b691b856 Assorted upgrades 2021-04-06 17:35:38 +02:00
Ivan Fedorov
acfe87fbc4 Flag Ordering#explicit invocations listing a subset of an enum's values (#14) 2021-03-30 08:19:08 +02:00
Stephan Schroevers
091a6eee7a Fix the fork configuration 2021-03-30 08:04:01 +02:00
Stephan Schroevers
3c06e3ead3 Some consistency changes 2021-03-28 17:28:00 +02:00
Stephan Schroevers
3ecab2f4b9 Reduce memory assigned to Surefire
We may need to reintroduce this change in the future, but for now we can
keep the configuration in sync with other parent POMs.
2021-03-28 15:05:09 +02:00
Stephan Schroevers
4b3e79667d Address deprecations in the upcoming release 2021-03-28 15:05:09 +02:00
Stephan Schroevers
6505535525 Upgrades 2021-03-27 15:28:20 +01:00
Stephan Schroevers
e48bbf3a44 Upgrade to Error Prone 2.5.1 2021-03-27 15:03:50 +01:00
Stephan Schroevers
3391468746 Assorted upgrades 2021-03-20 19:03:37 +01:00
Stephan Schroevers
10172c426d Assorted upgrades 2021-03-14 10:15:00 +01:00
Stephan Schroevers
f2737b4fe9 Fix MissingRefasterAnnotationCheck 2021-03-11 16:37:22 +01:00
Anna Dvorkin
f9a1c82d68 PRP-10968 Flag discouraged time zone-dependent APIs (#9) 2021-03-01 23:08:19 +01:00
Halil İbrahim Şener
cbc886d0c2 PSM-442 Flag likely-wrong @JsonCreator usages (#2) 2021-03-01 18:50:58 +01:00
Rick Ossendrijver
e4e3aded84 Flag methods that appear to lack a Refaster annotation (#13) 2021-02-28 16:29:51 +01:00
Rick Ossendrijver
e84d0e1059 Flag unordered annotation declarations (#11) 2021-02-28 14:42:44 +01:00
Stephan Schroevers
5e763d06d0 Assorted upgrades 2021-02-27 08:22:07 +01:00
Stephan Schroevers
ec05456c96 Choose Arrays.stream over Stream.of 2021-02-27 08:22:07 +01:00
Phil Werli
f53c47b56f PSM-585 Flag controller method parameters lacking annotations (#4) 2021-02-15 20:45:26 +01:00
Rick Ossendrijver
8a4c6e1209 Drop deprecated @BugPattern parameter and fix typos (#12) 2021-02-15 16:41:02 +01:00
Rick Ossendrijver
89033e2216 Add a few ImmutableCollection Refaster templates (#8) 2021-02-14 14:57:00 +01:00
Stephan Schroevers
4dc955a976 Assorted upgrades 2021-02-14 13:40:38 +01:00
331 changed files with 15549 additions and 3704 deletions

51
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,51 @@
---
name: 🐛 Bug report
about: Create a report to help us improve.
title: ""
labels: bug
assignees: ""
---
### Describe the bug
<!-- Provide a clear and concise description of what the bug or issue is. -->
- [ ] I have verified that the issue is reproducible against the latest version
of the project.
- [ ] I have searched through existing issues to verify that this issue is not
already known.
### Minimal Reproducible Example
<!-- Provide a clear and concise description of what happened. Please include
steps on how to reproduce the issue. -->
```java
If applicable and possible, please replace this section with the code that
triggered the isue.
```
<details>
<summary>Logs</summary>
```sh
Please replace this sentence with log output, if applicable.
```
</details>
### Expected behavior
<!-- Provide a clear and concise description of what you expected to happen. -->
### Setup
<!-- Please complete the following information: -->
- Operating system (e.g. MacOS Monterey).
- Java version (i.e. `java --version`, e.g. `17.0.3`).
- Error Prone version (e.g. `2.15.0`).
- Error Prone Support version (e.g. `0.3.0`).
### Additional context
<!-- Provide any other context about the problem here. -->

View File

@@ -0,0 +1,57 @@
---
name: 🚀 Request a new feature
about: Suggest a new feature.
title: ""
labels: new feature
assignees: ""
---
### Problem
<!-- Here, describe the context of the problem that you're facing, and which
you'd like to be solved through Error Prone Support. -->
### Description of the proposed new feature
<!-- Please indicate the type of improvement. -->
- [ ] Support a stylistic preference.
- [ ] Avoid a common gotcha, or potential problem.
<!--
Here, provide a clear and concise description of the desired change.
If possible, provide a simple and minimal example using the following format:
I would like to rewrite the following code:
```java
// XXX: Write the code to match here.
```
to:
```java
// XXX: Write the desired code here.
```
-->
### Considerations
<!--
Here, mention any other aspects to consider. Relevant questions:
- If applicable, is the rewrite operation a clear improvement in all cases?
- Are there special cases to consider?
- Can we further generalize the proposed solution?
- Are there alternative solutions we should consider?
-->
### Participation
<!-- Pull requests are very welcome, and we happily review contributions. Are
you up for the challenge? :D -->
- [ ] I am willing to submit a pull request to implement this improvement.
### Additional context
<!-- Provide any other context about the request here. -->

29
.github/release.yml vendored Normal file
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:
- "*"

38
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Build and verify
on:
pull_request:
push:
branches: [$default-branch]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
jdk: [ 11.0.16, 17.0.4 ]
steps:
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
# on Maven Central, and once against the Picnic Error Prone fork,
# additionally enabling all checks defined in this project and any
# Error Prone checks available only from other artifact repositories.
- name: Check out code
uses: actions/checkout@v3.0.2
- name: Set up JDK
uses: actions/setup-java@v3.5.1
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
cache: maven
- name: Display build environment details
run: mvn --version
- name: Build project against vanilla Error Prone, compile Javadoc
run: mvn -T1C install javadoc:jar
- name: Build project with self-check against Error Prone fork
run: mvn -T1C clean verify -Perror-prone-fork -Pnon-maven-central -Pself-check -s settings.xml
- name: Remove installed project artifacts
run: mvn build-helper:remove-project-artifact
# XXX: Enable Codecov once we "go public".
# XXX: Enable SonarCloud once we "go public".

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

@@ -0,0 +1,45 @@
name: Update `error-prone.picnic.tech` website content
on:
pull_request:
push:
branches: [$default-branch]
permissions:
contents: read
id-token: write
pages: 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: Configure Github Pages
uses: actions/configure-pages@v2.1.1
- name: Generate documentation
run: ./generate-docs.sh
- name: Build website with Jekyll
uses: actions/jekyll-build-pages@v1.0.5
with:
source: website/
destination: ./_site
- name: Validate HTML output
uses: anishathalye/proof-html@v1.4.1
with:
directory: ./_site
check_external_hash: false
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.4
deploy:
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
needs: build
runs-on: ubuntu-22.04
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1.2.1

13
.mvn/jvm.config Normal file
View File

@@ -0,0 +1,13 @@
-XX:ReservedCodeCacheSize=512m
-XX:SoftRefLRUPolicyMSPerMB=10
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--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.comp=ALL-UNNAMED

1
.mvn/maven.config Normal file
View File

@@ -0,0 +1 @@
--batch-mode --errors --strict-checksums

22
.renovaterc.json Normal file
View File

@@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"matchPackagePatterns": [
"^org\\.springframework:spring-framework-bom$",
"^org\\.springframework\\.boot:spring-boot[a-z-]*$"
],
"separateMinorPatch": true
},
{
"matchPackagePatterns": [
"^com\\.palantir\\.baseline:baseline-error-prone$"
],
"schedule": "* * 1 * *"
}
],
"reviewers": [
"rickie",
"Stephan202"
]
}

View File

@@ -1,37 +0,0 @@
---
dist: bionic
language: java
jdk: openjdk11
addons:
sonarcloud:
organization: picnic-technologies
token: "${SONARCLOUD_TOKEN}"
install:
- mvn io.takari:maven:wrapper
script:
# We run the build twice: once against the original Error Prone release,
# using only Error Prone checks available on Maven Central, and once against
# the Picnic Error Prone fork, additionally enabling Error Prone checks
# available from other artifact repositories.
- ./mvnw clean install
- ./mvnw clean install -Perror-prone-fork -Pnon-maven-central -s settings.xml
# XXX: Enable SonarCloud once we "go public".
# ./mvnw jacoco:prepare-agent surefire:test jacoco:report sonar:sonar
- ./mvnw jacoco:prepare-agent surefire:test jacoco:report
before_cache:
# Don't cache the artifacts we just generated, for multiple reasons: (1) we
# shouldn't need them next time around and (2) if we do, that indicates a
# dependency issue which might otherwise go unnoticed until next time we bump
# the project's version (i.e., when tagging).
- find "${HOME}/.m2/repository" -depth -name '*-SNAPSHOT' -exec rm -r '{}' \;
cache:
directories:
# The local Maven repository in which third party dependencies are stored.
- ${HOME}/.m2/repository
# The Takari Maven Wrapper's storage for downloaded Maven distributions.
- ${HOME}/.m2/wrapper
# The SonarQube analysis cache.
- ${HOME}/.sonar/cache
# XXX: Enable Codecov once we "go public".
#after_success:
# - bash <(curl -s https://codecov.io/bash)

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 that addresses a relatively
trivial stylistic concern it is doubly important that the violations it
detects can be auto-patched.
- Make sure you have read Error Prone's [criteria for new
checks][error-prone-criteria]. Most guidelines described there apply to this
project as well, except that this project _does_ focus quite heavy on style
enforcement. But that just makes the previous point doubly important.
- Make sure that a check's [(mutation) test
coverage][error-prone-support-mutation-tests] is or remains about as high as
it can be. Not only does this lead to better tests, it also points out
opportunities to simplify the code.
- Please restrict the scope of a pull request to a single feature or fix. Don't
sneak in unrelated changes; instead just open more than one pull request 😉.
[error-prone-criteria]: https://errorprone.info/docs/criteria
[error-prone-support-issues]: https://github.com/PicnicSupermarket/error-prone-support/issues
[error-prone-support-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh
[error-prone-support-pulls]: https://github.com/PicnicSupermarket/error-prone-support/pulls

21
LICENSE.md Normal file
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.

226
README.md Normal file
View File

@@ -0,0 +1,226 @@
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="website/assets/images/logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="website/assets/images/logo.svg">
<img alt="Error Prone Support logo" src="website/assets/images/logo.svg" width="50%">
</picture>
</div>
# Error Prone Support
Error Prone Support is a [Picnic][picnic-blog]-opinionated extension of
Google's [Error Prone][error-prone-orig-repo]. It aims to improve code quality,
focussing on maintainability, consistency and avoidance of common pitfalls.
> Error Prone is a static analysis tool for Java that catches common
> programming mistakes at compile-time.
[![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 templates. -->
<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.refastertemplates.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] template capable of
[rewriting][refaster-templates-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
templates][refaster-templates].
## 👷 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
[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-templates-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refastertemplates/BigDecimalTemplates.java
[refaster-templates]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refastertemplates/

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Compiles the code using Error Prone and applies its suggestions. The set of
# checks applied can optionally be restricted by name.
#
# As this script may modify the project's code, it is important to execute it
# in a clean Git working directory.
set -e -u -o pipefail
if [ "${#}" -gt 1 ]; then
echo "Usage: ./$(basename "${0}") [PatchChecks]"
exit 1
fi
patchChecks=${1:-}
mvn clean test-compile fmt:format \
-T 1.0C \
-Perror-prone \
-Perror-prone-fork \
-Ppatch \
-Pself-check \
-Derror-prone.patch-checks="${patchChecks}" \
-Dverification.skip

53
docgen/pom.xml Normal file
View File

@@ -0,0 +1,53 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.3.1-SNAPSHOT</version>
</parent>
<artifactId>docgen</artifactId>
<name>Picnic :: Error Prone Support :: Docgen</name>
<description>Docgen.</description>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,15 @@
package tech.picnic.errorprone.plugin;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.TaskEvent;
import tech.picnic.errorprone.plugin.objects.BugPatternData;
public final class BugPatternExtractor implements DocExtractor<BugPatternData> {
@Override
public BugPatternData extractData(ClassTree tree, TaskEvent taskEvent, VisitorState state) {
BugPattern annotation = taskEvent.getTypeElement().getAnnotation(BugPattern.class);
return BugPatternData.create(annotation, taskEvent.getTypeElement().getSimpleName().toString());
}
}

View File

@@ -0,0 +1,113 @@
package tech.picnic.errorprone.plugin;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.methodIsNamed;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TreeScanner;
import java.util.List;
import javax.annotation.Nullable;
import tech.picnic.errorprone.plugin.objects.BugPatternTestData;
public final class BugPatternTestsExtractor implements DocExtractor<BugPatternTestData> {
private static final Matcher<MethodTree> BUG_PATTERN_TEST =
allOf(
hasAnnotation("org.junit.jupiter.api.Test"),
anyOf(methodIsNamed("replacement"), methodIsNamed("identification")));
private static final Matcher<ExpressionTree> IDENTIFICATION_SOURCE_LINES =
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines");
private static final Matcher<ExpressionTree> REPLACEMENT_INPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines");
private static final Matcher<ExpressionTree> REPLACEMENT_OUTPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
@Override
public BugPatternTestData extractData(ClassTree tree, TaskEvent taskEvent, VisitorState state) {
String name = tree.getSimpleName().toString().replace("Test", "");
ScanBugCheckerTestData scanner = new ScanBugCheckerTestData(state);
tree.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(m -> BUG_PATTERN_TEST.matches(m, state))
.forEach(m -> scanner.scan(m, null));
return BugPatternTestData.create(
name, scanner.getIdentification(), scanner.getInput(), scanner.getOutput());
}
private static final class ScanBugCheckerTestData extends TreeScanner<Void, Void> {
private final VisitorState state;
private String identification;
private String input;
private String output;
ScanBugCheckerTestData(VisitorState state) {
this.state = state;
}
public String getIdentification() {
return identification;
}
public String getInput() {
return input;
}
public String getOutput() {
return output;
}
@Nullable
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void unused) {
if (IDENTIFICATION_SOURCE_LINES.matches(node, state)) {
identification = getSourceLines(node);
} else if (REPLACEMENT_INPUT.matches(node, state)) {
input = getSourceLines(node);
} else if (REPLACEMENT_OUTPUT.matches(node, state)) {
output = getSourceLines(node);
}
return super.visitMethodInvocation(node, unused);
}
private String getSourceLines(MethodInvocationTree tree) {
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
return getConstantSourceCode(sourceLines);
}
private String getConstantSourceCode(List<? extends ExpressionTree> sourceLines) {
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return "";
}
source.append(value).append('\n');
}
return source.toString();
}
}
}

View File

@@ -0,0 +1,9 @@
package tech.picnic.errorprone.plugin;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.TaskEvent;
public interface DocExtractor<T> {
T extractData(ClassTree tree, TaskEvent taskEvent, VisitorState state);
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.plugin;
import com.google.auto.service.AutoService;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.tools.javac.api.BasicJavacTask;
/**
* A variant of {@code com.google.errorprone.refaster.RefasterRuleCompiler} that outputs a {@code
* fully/qualified/Class.refaster} file for each compiled {@code fully.qualified.Class} that
* contains a Refaster template.
*/
@AutoService(Plugin.class)
public final class Docgen implements Plugin {
@Override
public String getName() {
return getClass().getSimpleName();
}
@Override
public void init(JavacTask javacTask, String... args) {
javacTask.addTaskListener(
new DocgenTaskListener(((BasicJavacTask) javacTask).getContext(), args[0]));
}
}

View File

@@ -0,0 +1,24 @@
package tech.picnic.errorprone.plugin;
public enum DocgenPart {
BUGPATTERN("bug-pattern-test-data.jsonl", new BugPatternExtractor()),
BUGPATTERN_TEST("bug-pattern-data.jsonl", new BugPatternTestsExtractor()),
REFASTER_TEMPLATE_TEST_INPUT("refaster-test-input-data.jsonl", new RefasterTestExtractor()),
REFASTER_TEMPLATE_TEST_OUTPUT("refaster-test-output-data.jsonl", new RefasterTestExtractor());
private final String dataFileName;
private final DocExtractor<?> extractor;
DocgenPart(String dataFileName, DocExtractor<?> extractor) {
this.dataFileName = dataFileName;
this.extractor = extractor;
}
public DocExtractor<?> getExtractor() {
return extractor;
}
public String getDataFileName() {
return dataFileName;
}
}

View File

@@ -0,0 +1,95 @@
package tech.picnic.errorprone.plugin;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.util.Context;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Optional;
import javax.tools.JavaFileObject;
/** XXX: Write this. */
final class DocgenTaskListener implements TaskListener {
private final Context context;
private final String basePath;
private final VisitorState state;
private final ObjectMapper mapper =
new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
DocgenTaskListener(Context context, String path) {
this.context = context;
this.basePath = path.substring(path.indexOf('=') + 1);
this.state = VisitorState.createForUtilityPurposes(context);
}
@Override
@SuppressWarnings("SystemOut")
public void finished(TaskEvent taskEvent) {
ClassTree tree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
if (tree == null || taskEvent.getSourceFile() == null) {
return;
}
getDocgenPart(tree, taskEvent)
.ifPresent(
docgenPart ->
writeToFile(
docgenPart.getExtractor().extractData(tree, taskEvent, state),
docgenPart.getDataFileName()));
}
private static Optional<DocgenPart> getDocgenPart(ClassTree tree, TaskEvent taskEvent) {
JavaFileObject sourceFile = taskEvent.getSourceFile();
if (isBugPatternTest(tree)) {
return Optional.of(DocgenPart.BUGPATTERN_TEST);
} else if (isBugPattern(tree)) {
return Optional.of(DocgenPart.BUGPATTERN);
} else if (sourceFile.getName().contains("TestInput")) {
return Optional.of(DocgenPart.REFASTER_TEMPLATE_TEST_INPUT);
} else if (sourceFile.getName().contains("TestOutput")) {
return Optional.of(DocgenPart.REFASTER_TEMPLATE_TEST_OUTPUT);
} else {
return Optional.empty();
}
}
private <T> void writeToFile(T data, String fileName) {
File file = new File(basePath + "/" + fileName);
try (FileWriter fileWriter = new FileWriter(file, true)) {
mapper.writeValue(fileWriter, data);
fileWriter.write("\n");
} catch (IOException e) {
e.printStackTrace();
}
}
private static boolean isBugPattern(ClassTree tree) {
return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName());
}
private static boolean isBugPatternTest(ClassTree tree) {
return tree.getSimpleName().toString().endsWith("Test")
&& tree.getMembers().stream()
.filter(VariableTree.class::isInstance)
.map(VariableTree.class::cast)
.anyMatch(
member -> member.getType().toString().equals("BugCheckerRefactoringTestHelper"));
}
}

View File

@@ -0,0 +1,32 @@
package tech.picnic.errorprone.plugin;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TaskEvent;
import tech.picnic.errorprone.plugin.objects.RefasterTemplateTestData;
public class RefasterTestExtractor
implements DocExtractor<ImmutableList<RefasterTemplateTestData>> {
@Override
public ImmutableList<RefasterTemplateTestData> extractData(
ClassTree tree, TaskEvent taskEvent, VisitorState state) {
String templateCollectionName = tree.getSimpleName().toString().replace("Test", "");
return tree.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m ->
RefasterTemplateTestData.create(
templateCollectionName,
m.getName().toString().replace("test", ""),
m.toString()))
.collect(toImmutableList());
}
}

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.plugin.objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import java.util.Arrays;
@AutoValue
public abstract class BugPatternData {
public static BugPatternData create(BugPattern annotation, String name) {
return new AutoValue_BugPatternData(
name,
Arrays.toString(annotation.altNames()),
annotation.linkType(),
annotation.link(),
Arrays.toString(annotation.tags()),
annotation.summary(),
annotation.explanation(),
annotation.severity(),
annotation.disableable());
}
@JsonProperty
abstract String name();
// Should be String[]
@JsonProperty
abstract String altNames();
@JsonProperty
abstract LinkType linkType();
@JsonProperty
abstract String link();
@JsonProperty
// Should be String[]
abstract String tags();
@JsonProperty
abstract String summary();
@JsonProperty
abstract String explanation();
@JsonProperty
abstract SeverityLevel severityLevel();
@JsonProperty
abstract boolean disableable();
// SuppressionAnnotations?
// DocumentSuppression?
}

View File

@@ -0,0 +1,28 @@
package tech.picnic.errorprone.plugin.objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import javax.annotation.Nullable;
@AutoValue
public abstract class BugPatternTestData {
public static BugPatternTestData create(
String name, String identificationLines, String inputLines, String outputLines) {
return new AutoValue_BugPatternTestData(name, identificationLines, inputLines, outputLines);
}
@JsonProperty
abstract String name();
@Nullable
@JsonProperty
abstract String identificationLines();
@Nullable
@JsonProperty
abstract String inputLines();
@Nullable
@JsonProperty
abstract String outputLines();
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.plugin.objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import com.google.errorprone.BugPattern.SeverityLevel;
@AutoValue
public abstract class RefasterTemplateData {
public static RefasterTemplateData create(
String name, String description, String link, SeverityLevel severityLevel) {
return new AutoValue_RefasterTemplateData(name, description, link, severityLevel);
}
@JsonProperty
abstract String name();
@JsonProperty
abstract String description();
@JsonProperty
abstract String link();
@JsonProperty
abstract SeverityLevel severityLevel();
}

View File

@@ -0,0 +1,22 @@
package tech.picnic.errorprone.plugin.objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
@AutoValue
public abstract class RefasterTemplateTestData {
public static RefasterTemplateTestData create(
String templateCollection, String templateName, String templateTestContent) {
return new AutoValue_RefasterTemplateTestData(
templateCollection, templateName, templateTestContent);
}
@JsonProperty
abstract String templateCollection();
@JsonProperty
abstract String templateName();
@JsonProperty
abstract String templateTestContent();
}

View File

@@ -0,0 +1,4 @@
/** A Java compiler plugin that XXX: fill in. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
package tech.picnic.errorprone.plugin;

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:
@@ -103,7 +54,7 @@ The following is a list of checks we'd like to see implemented:
signature groups. Using Error Prone's method matchers forbidden method calls
can easily be identified. But Error Prone can go one step further by
auto-patching violations. For each violation two fixes can be proposed: a
purely behavior-preserving fix which makes the platform-dependent behavior
purely behavior-preserving fix, which makes the platform-dependent behavior
explicit, and another which replaces the platform-dependent behavior with the
preferred alternative. (Such as using `UTF-8` instead of the system default
charset.)
@@ -113,128 +64,128 @@ The following is a list of checks we'd like to see implemented:
functionality.
- A subset of the refactor operations provided by the Eclipse-specific
[AutoRefactor][autorefactor] plugin.
- A check which replaces fully qualified types with simple types in contexts
- A check that replaces fully qualified types with simple types in contexts
where this does not introduce ambiguity. Should consider both actual Java
code and Javadoc `@link` references.
- A check which simplifies array expressions. It would replace empty array
- A check that simplifies array expressions. It would replace empty array
expressions of the form `new int[] {}` with `new int[0]`. Statements of the
form `byte[] arr = new byte[] {'c'};` would be shortened to `byte[] arr =
{'c'};`.
- A check which replaces expressions of the form `String.format("some prefix
- A check that replaces expressions of the form `String.format("some prefix
%s", arg)` with `"some prefix " + arg`, and similar for simple suffixes. Can
perhaps be generalized further, though it's unclear how far. (Well, a
`String.format` call without arguments can certainly be simplified, too.)
- A check which replaces single-character strings with `char`s where possible.
- A check that replaces single-character strings with `char`s where possible.
For example as argument to `StringBuilder.append` and in string
concatenations.
- A check which adds or removes the first `Locale` argument to `String.format`
- A check that adds or removes the first `Locale` argument to `String.format`
and similar calls as necessary. (For example, a format string containing only
`%s` placeholders is locale-insensitive unless any of the arguments is a
`Formattable`, while `%f` placeholders _are_ locale-sensitive.)
- A check which replaces `String.replaceAll` with `String.replace` if the first
- A check that replaces `String.replaceAll` with `String.replace` if the first
argument is certainly not a regular expression. And if both arguments are
single-character strings then the `(char, char)` overload can be invoked
instead.
- A check which flags (and ideally, replaces) `try-finally` constructs with
- A check that flags (and ideally, replaces) `try-finally` constructs with
equivalent `try-with-resources` constructs.
- A check which drops exceptions declared in `throws` clauses if they are (a)
- A check that drops exceptions declared in `throws` clauses if they are (a)
not actually thrown and (b) the associated method cannot be overridden.
- A check which tries to statically import certain methods whenever used, if
- A check that tries to statically import certain methods whenever used, if
possible. The set of targeted methods should be configurable, but may default
to e.g. `java.util.Function.identity()`, the static methods exposed by
`java.util.stream.Collectors` and the various Guava collector factory
methods.
- A check which replaces `new Random().someMethod()` calls with
`ThreadLocalRandom.current().someMethod()` calls, so as to avoid unnecessary
- A check that replaces `new Random().someMethod()` calls with
`ThreadLocalRandom.current().someMethod()` calls, to avoid unnecessary
synchronization.
- A check which drops `this.` from `this.someMethod()` calls and which
- A check that drops `this.` from `this.someMethod()` calls and which
optionally does the same for fields, if no ambiguity arises.
- A check which replaces `Integer.valueOf` calls with `Integer.parseInt` or
vice versa in order to prevent auto (un)boxing, and likewise for other number
- A check that replaces `Integer.valueOf` calls with `Integer.parseInt` or vice
versa in order to prevent auto (un)boxing, and likewise for other number
types.
- A check which flags nullable collections.
- A check which flags `AutoCloseable` resources not managed by a
- A check that flags nullable collections.
- A check that flags `AutoCloseable` resources not managed by a
`try-with-resources` construct. Certain subtypes, such as jOOQ's `DSLContext`
should be excluded.
- A check which flags `java.time` methods which implicitly consult the system
- A check that flags `java.time` methods which implicitly consult the system
clock, suggesting that a passed-in `Clock` is used instead.
- A check which flags public methods on public classes which reference
- A check that flags public methods on public classes which reference
non-public types. This can cause `IllegalAccessError`s and
`BootstrapMethodError`s at runtime.
- A check which swaps the LHS and RHS in expressions of the form
- A check that swaps the LHS and RHS in expressions of the form
`nonConstant.equals(someNonNullConstant)`.
- A check which annotates methods which only throw an exception with
- A check that annotates methods which only throw an exception with
`@Deprecated` or ` @DoNotCall`.
- A check which flags imports from other test classes.
- A Guava-specific check which replaces `Joiner.join` calls with `String.join`
- A check that flags imports from other test classes.
- A Guava-specific check that replaces `Joiner.join` calls with `String.join`
calls in those cases where the latter is a proper substitute for the former.
- A Guava-specific check which flags `{Immutable,}Multimap` type usages
where `{Immutable,}{List,Set}Multimap` would be more appropriate.
- A Guava-specific check which rewrites `if (conditional) { throw new
- A Guava-specific check that flags `{Immutable,}Multimap` type usages where
`{Immutable,}{List,Set}Multimap` would be more appropriate.
- A Guava-specific check that rewrites `if (conditional) { throw new
IllegalArgumentException(); }` and variants to an equivalent `checkArgument`
statement. Idem for other exception types.
- A Guava-specific check which replaces simple anonymous `CacheLoader` subclass
- A Guava-specific check that replaces simple anonymous `CacheLoader` subclass
declarations with `CacheLoader.from(someLambda)`.
- A Spring-specific check which enforces that methods with the `@Scheduled`
- A Spring-specific check that enforces that methods with the `@Scheduled`
annotation are also annotated with New Relic's `@Trace` annotation. Such
methods should ideally not also represent Spring MVC endpoints.
- A Spring-specific check which enforces that `@RequestMapping` annotations,
- A Spring-specific check that enforces that `@RequestMapping` annotations,
when applied to a method, explicitly specify one or more target HTTP methods.
- A Spring-specific check which looks for classes in which all
`@RequestMapping` annotations (and the various aliases) specify the same
`path`/`value` property and then moves that path to the class level.
- A Spring-specific check which flags `@Value("some.property")` annotations, as
- A Spring-specific check that looks for classes in which all `@RequestMapping`
annotations (and the various aliases) specify the same `path`/`value`
property and then moves that path to the class level.
- A Spring-specific check that flags `@Value("some.property")` annotations, as
these almost certainly should be `@Value("${some.property}")`.
- A Spring-specific check which drops the `required` attribute from
- A Spring-specific check that drops the `required` attribute from
`@RequestParam` annotations when the `defaultValue` attribute is also
specified.
- A Spring-specific check which rewrites a class which uses field injection to
- A Spring-specific check that rewrites a class which uses field injection to
one which uses constructor injection. This check wouldn't be strictly
behavior preserving, but could be used for a one-off code base migration.
- A Spring-specific check which disallows field injection, except in
- A Spring-specific check that disallows field injection, except in
`AbstractTestNGSpringContextTests` subclasses. (One known edge case:
self-injections so that a bean can call itself through an implicit proxy.)
- A Spring-specific check which verifies that public methods on all classes
- A Spring-specific check that verifies that public methods on all classes
whose name matches a certain pattern, e.g. `.*Service`, are annotated
`@Secured`.
- A Spring-specific check which verifies that annotations such as
- A Spring-specific check that verifies that annotations such as
`@RequestParam` are only present in `@RestController` classes.
- A Spring-specific check which disallows `@ResponseStatus` on MVC endpoint
- A Spring-specific check that disallows `@ResponseStatus` on MVC endpoint
methods, as this prevents communication of error status codes.
- A Hibernate Validator-specific check which looks for `@UnwrapValidatedValue`
- A Hibernate Validator-specific check that looks for `@UnwrapValidatedValue`
usages and migrates the associated constraint annotations to the generic type
argument to which they (are presumed to) apply.
- A TestNG-specific check which drops method-level `@Test` annotations if a
- A TestNG-specific check that drops method-level `@Test` annotations if a
matching/more specific annotation is already present at the class level.
- A TestNG-specific check which enforces that all tests are in a group.
- A TestNG-specific check which flags field assignments in
- A TestNG-specific check that enforces that all tests are in a group.
- A TestNG-specific check that flags field assignments in
`@BeforeMethod`-annotated methods unless the class is annotated
`@Test(singleThreaded = true)`.
- A TestNG-specific check which flags usages of the `expectedExceptions`
- A TestNG-specific check that flags usages of the `expectedExceptions`
attribute of the `@Test` annotation, pointing to `assertThrows`.
- A Jongo-specific check which disallows the creation of sparse indices, in
- A Jongo-specific check that disallows the creation of sparse indices, in
favour of partial indices.
- An Immutables-specific check which replaces
- An Immutables-specific check that replaces
`checkState`/`IllegalStateException` usages inside a `@Value.Check`-annotated
method with `checkArgument`/`IllegalArgument`, since the method is invoked
when a caller attempts to create an immutable instance.
- An Immutables-specific check which disallows references to collection types
- An Immutables-specific check that disallows references to collection types
other than the Guava immutable collections, including inside generic type
arguments.
- An SLF4J-specific check which drops or adds a trailing dot from log messages,
- An SLF4J-specific check that drops or adds a trailing dot from log messages,
as applicable.
- A Mockito-specific check which identifies sequences of statements which mock
a significant number of methods on a single object with "default data"; such
- A Mockito-specific check that identifies sequences of statements which mock a
significant number of methods on a single object with "default data"; such
constructions can often benefit from a different type of default answer, such
as `Answers.RETURNS_MOCKS`.
- An RxJava-specific check which flags `.toCompletable()` calls on expressions
- An RxJava-specific check that flags `.toCompletable()` calls on expressions
of type `Single<Completable>` etc., as most likely
`.flatMapCompletable(Functions.identity())` was meant instead. Idem for other
variations.
- An RxJava-specific check which flags `expr.firstOrError()` calls and suggests
- An RxJava-specific check that flags `expr.firstOrError()` calls and suggests
`expr.switchIfEmpty(Single.error(...))`, so that an application-specific
exception is thrown instead of `NoSuchElementException`.
- An RxJava-specific check which flags use of `#assertValueSet` without
- An RxJava-specific check that flags use of `#assertValueSet` without
`#assertValueCount`, as the former method doesn't do what one may intuitively
expect it to do. See ReactiveX/RxJava#6151.
@@ -247,6 +198,7 @@ but on the flip side Refaster is much less expressive. While this gap can never
be fully closed, there are some ways in which Refaster's scope of utility could
be extended. The following is a non-exhaustive list of ideas on how to extend
Refaster's expressiveness:
- Allow more control over _which_ methods are statically imported by
`@UseImportPolicy`. Sometimes the `@AfterTemplate` contains more than one
static method invocation, and only a subset should be statically imported.
@@ -261,9 +213,9 @@ Refaster's expressiveness:
pure expression. Introduce a way to express such a constraint. For example,
rewriting `optional1.map(Optional::of).orElse(optional2)` to `optional1.or(()
-> optional2)` is not behavior preserving if evaluation of `optional2` has
side-effects.
side effects.
- Similarly, certain refactoring operations are only valid if one of the
matches expressions is not `@Nullable`. It'd be nice to be able to express
matched expressions is not `@Nullable`. It'd be nice to be able to express
this.
- Generalize `@Placeholder` support such that rules can reference e.g. "any
concrete unary method". This would allow refactorings such as
@@ -276,10 +228,6 @@ Refaster's expressiveness:
to be lost. In such a case don't statically import the method, so that the
generic type information can be retained. (There may be cases where generic
type information should even be _added_. Find an example.)
- Upon application of a template Refaster can throw a _No binding for
Key{identifier=someAfterTemplateParam}_ exception. When this happens the
template is invalid. Instead perform this check at compile time, such that
such malformed templates cannot be defined in the first place.
- Provide a way to express "match if (not) annotated (with _X_)". See #1 for a
motivating example.
- Provide a way to place match constraints on compile time constants. For
@@ -288,40 +236,36 @@ Refaster's expressiveness:
- Provide a way to express transformations of compile-time constants. This
would allow one to e.g. rewrite single-character strings to chars or vice
versa, thereby accommodating a target API. Another example would be to
replace SLF4J's `{}` place holders with `%s` or vice versa. Yet another
replace SLF4J's `{}` placeholders with `%s` or vice versa. Yet another
example would be to rewrite `BigDecimal.valueOf("<some-long-value>")` to
`BigDecimal.valueOf(theParsedLongValue)`.
- More generally, investigate ways to plug in in fully dynamic behavior, e.g.
by providing hooks using which arbitrary predicates/transformations can be
plugged in. The result would be a Refaster/`BugChecker` hybrid. A feature
such as this could form the basis for many other features listed here. (As a
concrete example, consider the ability to reference
- More generally, investigate ways to plug in fully dynamic behavior, e.g. by
providing hooks which enable plugging in arbitrary
predicates/transformations. The result would be a Refaster/`BugChecker`
hybrid. A feature like this could form the basis for many other features
listed here. (As a concrete example, consider the ability to reference
`com.google.errorprone.matchers.Matcher` implementations.)
- Provide a way to match lambda expressions and method references which match a
specified functional interface. This would allow rewrites such as
`Mono.fromCallable(this::doesNotThrowCheckException)` ->
`Mono.fromSupplier(this::doesNotThrowCheckException)`.
- Provide an extension API using which methods or expressions can be defined
based on functional properties. A motivating example is the Java Collections
- Provide an extension API that enables defining methods or expressions based
on functional properties. A motivating example is the Java Collections
framework, which allows many ways to define (im)mutable (un)ordered
collections with(out) duplicates. One could then express things like "match
any method call with collects its inputs into an immutable ordered list". An
any method call that collects its inputs into an immutable ordered list". An
enum analogous to `java.util.stream.Collector.Characteristics` could be used.
Out of the box JDK and Guava collection factory methods could be classified,
with the user having the option to extend the classification.
- Refaster currently unconditionally ignores expressions containing comments.
Provide two additional modes: (a) match and drop the comments or (b)
transport the comments to before/after the replaced expression.
- Extend Refaster to drop imports that come become unnecessary as a result of a
refactoring. This e.g. allows one to replace a statically import TestNG
- Extend Refaster to drop imports that become unnecessary as a result of a
refactoring. This e.g. allows one to replace a statically imported TestNG
`fail(...)` invocation with a statically imported equivalent AssertJ
`fail(...)` invocation. (Observe that without an impor cleanup this
`fail(...)` invocation. (Observe that without an import cleanup this
replacement would cause a compilation error.)
- Extend the `@Repeated` match semantics such that it also covers non-varargs
methods. For a motivating example see google/error-prone#568.
- When matching explicit type references, also match super types. For a
motivating example, see the two subtly difference loop definitions in
`CollectionRemoveAllFromCollectionBlock`.
motivating example, see the two subtly different loop definitions in
`CollectionRemoveAllFromCollectionExpression`.
- Figure out why Refaster sometimes doesn't match the correct generic overload.
See the `AssertThatIterableHasOneComparableElementEqualTo` template for an
example.
@@ -331,16 +275,12 @@ Refaster's expressiveness:
[checkstyle-external-project-tests]: https://github.com/checkstyle/checkstyle/blob/master/wercker.yml
[codecov]: https://codecov.io
[error-prone-bug-patterns]: https://errorprone.info/bugpatterns
[error-prone-criteria]: https://errorprone.info/docs/criteria
[error-prone-fork-jitpack]: https://jitpack.io/#PicnicSupermarket/error-prone
[error-prone-fork-repo]: https://github.com/PicnicSupermarket/error-prone
[error-prone]: https://errorprone.info
[error-prone-repo]: https://github.com/google/error-prone
[forbidden-apis]: https://github.com/policeman-tools/forbidden-apis
[fossa]: https://fossa.io
[google-java-format]: https://github.com/google/google-java-format
[maven]: https://maven.apache.org
[main-contributing]: ../CONTRIBUTING.md
[main-readme]: ../README.md
[modernizer-maven-plugin]: https://github.com/gaul/modernizer-maven-plugin
[sonarcloud]: https://sonarcloud.io
[pitest]: https://pitest.org
[pitest-maven]: https://pitest.org/quickstart/maven

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.3.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -41,17 +41,24 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-resource-compiler</artifactId>
<!-- This dependency is declared only as a hint to Maven that
compilation depends on it; see the `maven-compiler-plugin`'s
`annotationProcessorPaths` configuration below. -->
<artifactId>refaster-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
@@ -63,15 +70,19 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
@@ -102,6 +113,11 @@
<artifactId>swagger-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
@@ -118,10 +134,15 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@@ -154,18 +175,23 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
<artifactId>spring-test</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -177,15 +203,6 @@
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.coveo</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<configuration>
<additionalSourceDirectories>
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
</additionalSourceDirectories>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -193,16 +210,61 @@
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-resource-compiler</artifactId>
<artifactId>refaster-compiler</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleResourceCompiler</arg>
<arg>-Xplugin:RefasterRuleCompiler</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
<ignoredUnusedDeclaredDependencies>
<!-- XXX: Figure out why the plugin thinks this
dependency is unused. -->
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>docgen</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>add-test-source</id>
<goals>
<goal>add-test-source</goal>
</goals>
<phase>generate-test-sources</phase>
<configuration>
<sources>
<source>src/test/resources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,69 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import java.util.Map;
import javax.lang.model.element.AnnotationValue;
/** A {@link BugChecker} that flags ambiguous {@code @JsonCreator}s in enums. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "`JsonCreator.Mode` should be set for single-argument creators",
link = BUG_PATTERNS_BASE_URL + "AmbiguousJsonCreator",
linkType = CUSTOM,
severity = WARNING,
tags = LIKELY_ERROR)
public final class AmbiguousJsonCreator extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
isType("com.fasterxml.jackson.annotation.JsonCreator");
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
if (!IS_JSON_CREATOR_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
ClassTree clazz = state.findEnclosing(ClassTree.class);
if (clazz == null || clazz.getKind() != Tree.Kind.ENUM) {
return Description.NO_MATCH;
}
MethodTree method = state.findEnclosing(MethodTree.class);
if (method == null || method.getParameters().size() != 1) {
return Description.NO_MATCH;
}
boolean customMode =
ASTHelpers.getAnnotationMirror(tree).getElementValues().entrySet().stream()
.filter(entry -> entry.getKey().getSimpleName().contentEquals("mode"))
.map(Map.Entry::getValue)
.map(AnnotationValue::getValue)
.filter(Symbol.VarSymbol.class::isInstance)
.map(Symbol.VarSymbol.class::cast)
.anyMatch(varSymbol -> !varSymbol.getSimpleName().contentEquals("DEFAULT"));
return customMode
? Description.NO_MATCH
: describeMatch(
tree, SuggestedFix.replace(tree, "@JsonCreator(mode = JsonCreator.Mode.DELEGATING)"));
}
}

View File

@@ -0,0 +1,59 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.nullLiteral;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodInvocationTree;
/**
* A {@link BugChecker} that flags AssertJ {@code isEqualTo(null)} checks for simplification.
*
* <p>This bug checker cannot be replaced with a simple Refaster template, as the Refaster approach
* would require that all overloads of {@link org.assertj.core.api.Assert#isEqualTo(Object)} (such
* as {@link org.assertj.core.api.AbstractStringAssert#isEqualTo(String)}) are explicitly
* enumerated. This bug checker generically matches all such current and future overloads.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
link = BUG_PATTERNS_BASE_URL + "AssertJIsNull",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AssertJIsNull extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.Assert").named("isEqualTo"),
argumentCount(1),
argument(0, nullLiteral()));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fix =
SuggestedFix.builder().merge(SuggestedFixes.renameMethodInvocation(tree, "isNull", state));
tree.getArguments().forEach(arg -> fix.merge(SuggestedFix.delete(arg)));
return describeMatch(tree, fix.build());
}
}

View File

@@ -1,16 +1,17 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
@@ -24,16 +25,15 @@ import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.List;
/** A {@link BugChecker} which flags redundant {@code @Autowired} constructor annotations. */
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "AutowiredConstructor",
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class AutowiredConstructorCheck extends BugChecker implements ClassTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "AutowiredConstructor",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AutowiredConstructor extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
@@ -45,7 +45,7 @@ public final class AutowiredConstructorCheck extends BugChecker implements Class
return Description.NO_MATCH;
}
List<AnnotationTree> annotations =
ImmutableList<AnnotationTree> annotations =
AUTOWIRED_ANNOTATION
.multiMatchResult(Iterables.getOnlyElement(constructors), state)
.matchingNodes();

View File

@@ -1,12 +1,13 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
@@ -25,26 +26,25 @@ import java.util.Optional;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags annotations that could be written more concisely. */
/** A {@link BugChecker} that flags annotations that could be written more concisely. */
@AutoService(BugChecker.class)
@BugPattern(
name = "CanonicalAnnotationSyntax",
summary = "Omit redundant syntax from annotation declarations",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class CanonicalAnnotationSyntaxCheck extends BugChecker
implements AnnotationTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "CanonicalAnnotationSyntax",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
FIX_FACTORIES =
ImmutableSet.of(
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
CanonicalAnnotationSyntax::dropRedundantParentheses,
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
CanonicalAnnotationSyntax::dropRedundantCurlies);
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
@@ -104,7 +104,8 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
return Optional.of(
SuggestedFix.replace(
arg,
simplifyAttributeValue(expr, state).orElseGet(() -> Util.treeToString(expr, state))));
simplifyAttributeValue(expr, state)
.orElseGet(() -> SourceCode.treeToString(expr, state))));
}
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
@@ -139,11 +140,11 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
private static Optional<String> simplifySingletonArray(NewArrayTree array, VisitorState state) {
return Optional.of(array.getInitializers())
.filter(initializers -> initializers.size() == 1)
.map(initializers -> Util.treeToString(initializers.get(0), state));
.map(initializers -> SourceCode.treeToString(initializers.get(0), state));
}
private static Optional<String> dropTrailingComma(NewArrayTree array, VisitorState state) {
String src = Util.treeToString(array, state);
String src = SourceCode.treeToString(array, state);
return Optional.of(TRAILING_ARRAY_COMMA.matcher(src))
.filter(Matcher::find)
.map(m -> src.substring(0, m.start()) + '}');

View File

@@ -0,0 +1,119 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.stream.Collector;
/**
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
* (im)mutability.
*
* <p>Replacing such collectors with alternatives that produce immutable collections is preferred.
* Do note that Guava's immutable collections are null-hostile.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability",
link = BUG_PATTERNS_BASE_URL + "CollectorMutability",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
staticMethod().onClass("java.util.stream.Collectors");
private static final Matcher<ExpressionTree> LIST_COLLECTOR =
staticMethod().anyClass().named("toList");
private static final Matcher<ExpressionTree> MAP_COLLECTOR =
staticMethod().anyClass().named("toMap");
private static final Matcher<ExpressionTree> SET_COLLECTOR =
staticMethod().anyClass().named("toSet");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!COLLECTOR_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
if (LIST_COLLECTOR.matches(tree, state)) {
return suggestToCollectionAlternatives(
tree, "com.google.common.collect.ImmutableList.toImmutableList", "ArrayList", state);
}
if (MAP_COLLECTOR.matches(tree, state)) {
return suggestToMapAlternatives(tree, state);
}
if (SET_COLLECTOR.matches(tree, state)) {
return suggestToCollectionAlternatives(
tree, "com.google.common.collect.ImmutableSet.toImmutableSet", "HashSet", state);
}
return Description.NO_MATCH;
}
private Description suggestToCollectionAlternatives(
MethodInvocationTree tree,
String fullyQualifiedImmutableReplacement,
String mutableReplacement,
VisitorState state) {
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
String toCollectionSelect =
SuggestedFixes.qualifyStaticImport(
"java.util.stream.Collectors.toCollection", mutableFix, state);
return buildDescription(tree)
.addFix(replaceMethodInvocation(tree, fullyQualifiedImmutableReplacement, state))
.addFix(
mutableFix
.addImport(String.format("java.util.%s", mutableReplacement))
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableReplacement))
.build())
.build();
}
private Description suggestToMapAlternatives(MethodInvocationTree tree, VisitorState state) {
int argCount = tree.getArguments().size();
if (argCount > 3) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(
replaceMethodInvocation(
tree, "com.google.common.collect.ImmutableMap.toImmutableMap", state))
.addFix(
SuggestedFix.builder()
.addImport("java.util.HashMap")
.postfixWith(
tree.getArguments().get(argCount - 1),
(argCount == 2 ? ", (a, b) -> { throw new IllegalStateException(); }" : "")
+ ", HashMap::new")
.build())
.build();
}
private static SuggestedFix replaceMethodInvocation(
MethodInvocationTree tree, String fullyQualifiedReplacement, VisitorState state) {
SuggestedFix.Builder fix = SuggestedFix.builder();
String replacement = SuggestedFixes.qualifyStaticImport(fullyQualifiedReplacement, fix, state);
fix.merge(SuggestedFix.replace(tree.getMethodSelect(), replacement));
return fix.build();
}
}

View File

@@ -1,15 +1,16 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
@@ -17,22 +18,22 @@ import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import java.util.Optional;
/** A {@link BugChecker} which flags empty methods that seemingly can simply be deleted. */
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@BugPattern(
name = "EmptyMethod",
summary = "Empty method can likely be deleted",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = BugPattern.ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "EmptyMethod",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<Tree> HAS_PERMITTED_ANNOTATION =
private static final Matcher<Tree> PERMITTED_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
@@ -42,15 +43,22 @@ public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatc
if (tree.getBody() == null
|| !tree.getBody().getStatements().isEmpty()
|| ASTHelpers.containsComments(tree, state)
|| HAS_PERMITTED_ANNOTATION.matches(tree, state)) {
|| PERMITTED_ANNOTATION.matches(tree, state)
|| isInPossibleTestHelperClass(state)) {
return Description.NO_MATCH;
}
MethodSymbol sym = ASTHelpers.getSymbol(tree);
if (sym == null || ASTHelpers.methodCanBeOverridden(sym)) {
if (ASTHelpers.methodCanBeOverridden(ASTHelpers.getSymbol(tree))) {
return Description.NO_MATCH;
}
return describeMatch(tree, SuggestedFix.delete(tree));
}
private static boolean isInPossibleTestHelperClass(VisitorState state) {
return Optional.ofNullable(ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class))
.map(ClassTree::getSimpleName)
.filter(name -> name.toString().contains("Test"))
.isPresent();
}
}

View File

@@ -0,0 +1,162 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import com.google.googlejavaformat.java.ImportOrderer;
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
import com.google.googlejavaformat.java.RemoveUnusedImports;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.Position;
import java.util.List;
import java.util.Optional;
/**
* A {@link BugChecker} that flags improperly formatted Error Prone test code.
*
* <p>All test code should be formatted in accordance with Google Java Format's {@link Formatter}
* output, and imports should be ordered according to the {@link Style#GOOGLE Google} style.
*
* <p>This checker inspects inline code passed to {@code
* com.google.errorprone.CompilationTestHelper} and {@code
* com.google.errorprone.BugCheckerRefactoringTestHelper}. It requires that this code is properly
* formatted and that its imports are organized. Only code that represents the expected output of a
* refactoring operation is allowed to have unused imports, as most {@link BugChecker}s do not (and
* are not able to) remove imports that become obsolete as a result of applying their suggested
* fix(es).
*/
// XXX: Once we target JDK 17 (optionally?) suggest text block fixes.
// XXX: GJF guesses the line separator to be used by inspecting the source. When using text blocks
// this may cause the current unconditional use of `\n` not to be sufficient when building on
// Windows; TBD.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Test code should follow the Google Java style",
link = BUG_PATTERNS_BASE_URL + "ErrorProneTestHelperSourceFormat",
linkType = CUSTOM,
severity = SUGGESTION,
tags = STYLE)
public final class ErrorProneTestHelperSourceFormat extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Formatter FORMATTER = new Formatter();
private static final Matcher<ExpressionTree> INPUT_SOURCE_ACCEPTING_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines"),
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines"));
private static final Matcher<ExpressionTree> OUTPUT_SOURCE_ACCEPTING_METHOD =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);
if (!isOutputSource && !INPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
if (sourceLines.isEmpty()) {
return buildDescription(tree).setMessage("No source code provided").build();
}
int startPos = ASTHelpers.getStartPosition(sourceLines.get(0));
int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1));
/* Attempt to format the source code only if it fully consists of constant expressions. */
return getConstantSourceCode(sourceLines)
.map(source -> flagFormattingIssues(startPos, endPos, source, isOutputSource, state))
.orElse(Description.NO_MATCH);
}
private Description flagFormattingIssues(
int startPos, int endPos, String source, boolean retainUnusedImports, VisitorState state) {
Tree methodInvocation = state.getPath().getLeaf();
String formatted;
try {
formatted = formatSourceCode(source, retainUnusedImports).trim();
} catch (FormatterException e) {
return buildDescription(methodInvocation)
.setMessage(String.format("Source code is malformed: %s", e.getMessage()))
.build();
}
if (source.trim().equals(formatted)) {
return Description.NO_MATCH;
}
if (startPos == Position.NOPOS || endPos == Position.NOPOS) {
/*
* We have insufficient source information to emit a fix, so we only flag the fact that the
* code isn't properly formatted.
*/
return describeMatch(methodInvocation);
}
/*
* The code isn't properly formatted; replace all lines with the properly formatted
* alternatives.
*/
return describeMatch(
methodInvocation,
SuggestedFix.replace(
startPos,
endPos,
Splitter.on('\n')
.splitToStream(formatted)
.map(state::getConstantExpression)
.collect(joining(", "))));
}
private static String formatSourceCode(String source, boolean retainUnusedImports)
throws FormatterException {
String withReorderedImports = ImportOrderer.reorderImports(source, Style.GOOGLE);
String withOptionallyRemovedImports =
retainUnusedImports
? withReorderedImports
: RemoveUnusedImports.removeUnusedImports(withReorderedImports);
return FORMATTER.formatSource(withOptionallyRemovedImports);
}
private static Optional<String> getConstantSourceCode(
List<? extends ExpressionTree> sourceLines) {
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return Optional.empty();
}
source.append(value).append('\n');
}
return Optional.of(source.toString());
}
}

View File

@@ -0,0 +1,91 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.stream.Collectors.collectingAndThen;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
/**
* A {@link BugChecker} that flags {@link Ordering#explicit(Object, Object[])}} invocations listing
* a subset of an enum type's values.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Make sure `Ordering#explicit` lists all of an enum's values",
link = BUG_PATTERNS_BASE_URL + "ExplicitEnumOrdering",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
staticMethod().onClass(Ordering.class.getName()).named("explicit");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!EXPLICIT_ORDERING.matches(tree, state)) {
return Description.NO_MATCH;
}
ImmutableSet<String> missingEnumValues = getMissingEnumValues(tree.getArguments());
if (missingEnumValues.isEmpty()) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
String.format(
"Explicit ordering lacks some enum values: %s",
String.join(", ", missingEnumValues)))
.build();
}
private static ImmutableSet<String> getMissingEnumValues(
List<? extends ExpressionTree> expressions) {
return expressions.stream()
.map(ASTHelpers::getSymbol)
.filter(Symbol::isEnum)
.collect(
collectingAndThen(
toImmutableSetMultimap(Symbol::asType, Symbol::toString),
ExplicitEnumOrdering::getMissingEnumValues));
}
private static ImmutableSet<String> getMissingEnumValues(
ImmutableSetMultimap<Type, String> valuesByType) {
return Multimaps.asMap(valuesByType).entrySet().stream()
.flatMap(e -> getMissingEnumValues(e.getKey(), e.getValue()))
.collect(toImmutableSet());
}
private static Stream<String> getMissingEnumValues(Type enumType, Set<String> values) {
Symbol.TypeSymbol typeSymbol = enumType.asElement();
return Sets.difference(ASTHelpers.enumValues(typeSymbol), values).stream()
.map(v -> String.format("%s.%s", typeSymbol.getSimpleName(), v));
}
}

View File

@@ -0,0 +1,87 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
/**
* A {@link BugChecker} that flags usages of {@link Flux#flatMap(Function)} and {@link
* Flux#flatMapSequential(Function)}.
*
* <p>{@link Flux#flatMap(Function)} and {@link Flux#flatMapSequential(Function)} eagerly perform up
* to {@link reactor.util.concurrent.Queues#SMALL_BUFFER_SIZE} subscriptions. Additionally, the
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
* avoids potentially saturating the thread pool on which subscription happens. If {@code
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
* {@code flatMapSequential} overload with an explicit concurrency level.
*
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
* by this check because there is no clear alternative to point to.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; "
+ "please use `Flux#concatMap` or explicitly specify the desired amount of concurrency",
link = BUG_PATTERNS_BASE_URL + "FluxFlatMapUsage",
linkType = CUSTOM,
severity = ERROR,
tags = LIKELY_ERROR)
public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf("flatMap", "flatMapSequential")
.withParameters(Function.class.getName());
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!FLUX_FLATMAP.matches(tree, state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
.addFix(
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build())
.build();
}
@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
if (!FLUX_FLATMAP.matches(tree, state)) {
return Description.NO_MATCH;
}
// Method references are expected to occur very infrequently; generating both variants of
// suggested fixes is not worth the trouble.
return describeMatch(tree);
}
}

View File

@@ -0,0 +1,259 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyMethod;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.SimpleTreeVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags string concatenations that produce a format string; in such cases
* the string concatenation should instead be deferred to the invoked method.
*
* @implNote This checker is based on the implementation of {@link
* com.google.errorprone.bugpatterns.flogger.FloggerStringConcatenation}.
*/
// XXX: Support arbitrary `@FormatMethod`-annotated methods.
// XXX: For (explicit or delegated) invocations of `java.util.Formatter` _strictly speaking_ we
// should introduce special handling of `Formattable` arguments, as this check would replace a
// `Formattable#toString` invocation with a `Formattable#formatTo` invocation. But likely that
// should be considered a bug fix, too.
// XXX: Introduce a separate check that adds/removes the `Locale` parameter to `String.format`
// invocations, as necessary.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Defer string concatenation to the invoked method",
link = BUG_PATTERNS_BASE_URL + "FormatStringConcatenation",
linkType = CUSTOM,
severity = WARNING,
tags = SIMPLIFICATION)
public final class FormatStringConcatenation extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
/**
* AssertJ exposes varargs {@code fail} methods with a {@link Throwable}-accepting overload, the
* latter of which should not be flagged.
*/
private static final Matcher<ExpressionTree> ASSERTJ_FAIL_WITH_THROWABLE_METHOD =
anyMethod()
.anyClass()
.withAnyName()
.withParameters(String.class.getName(), Throwable.class.getName());
// XXX: Drop some of these methods if we use Refaster to replace some with others.
private static final Matcher<ExpressionTree> ASSERTJ_FORMAT_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractAssert")
.namedAnyOf("overridingErrorMessage", "withFailMessage"),
allOf(
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractSoftAssertions")
.named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)),
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractStringAssert")
.named("isEqualTo"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractThrowableAssert")
.namedAnyOf(
"hasMessage",
"hasMessageContaining",
"hasMessageEndingWith",
"hasMessageStartingWith",
"hasRootCauseMessage",
"hasStackTraceContaining"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.Descriptable")
.namedAnyOf("as", "describedAs"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.ThrowableAssertAlternative")
.namedAnyOf(
"withMessage",
"withMessageContaining",
"withMessageEndingWith",
"withMessageStartingWith",
"withStackTraceContaining"),
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.WithAssertions").named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)),
allOf(
staticMethod()
.onClassAny(
"org.assertj.core.api.Assertions",
"org.assertj.core.api.BDDAssertions",
"org.assertj.core.api.Fail")
.named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)));
private static final Matcher<ExpressionTree> GUAVA_FORMAT_METHOD =
anyOf(
staticMethod()
.onClass("com.google.common.base.Preconditions")
.namedAnyOf("checkArgument", "checkNotNull", "checkState"),
staticMethod().onClass("com.google.common.base.Verify").named("verify"));
// XXX: Add `PrintWriter`, maybe others.
private static final Matcher<ExpressionTree> JDK_FORMAT_METHOD =
anyOf(
staticMethod().onClass("java.lang.String").named("format"),
instanceMethod().onExactClass("java.util.Formatter").named("format"));
private static final Matcher<ExpressionTree> SLF4J_FORMAT_METHOD =
instanceMethod()
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("debug", "error", "info", "trace", "warn");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (hasNonConstantStringConcatenationArgument(tree, 0, state)) {
return flagViolation(tree, ASSERTJ_FORMAT_METHOD, 0, "%s", state)
.or(() -> flagViolation(tree, JDK_FORMAT_METHOD, 0, "%s", state))
.or(() -> flagViolation(tree, SLF4J_FORMAT_METHOD, 0, "{}", state))
.orElse(Description.NO_MATCH);
}
if (hasNonConstantStringConcatenationArgument(tree, 1, state)) {
return flagViolation(tree, GUAVA_FORMAT_METHOD, 1, "%s", state)
.or(() -> flagViolation(tree, JDK_FORMAT_METHOD, 1, "%s", state))
.or(() -> flagViolation(tree, SLF4J_FORMAT_METHOD, 1, "{}", state))
.orElse(Description.NO_MATCH);
}
return Description.NO_MATCH;
}
/**
* Flags the given method invocation if it matches a targeted method and passes a non-compile time
* constant string concatenation as a format string.
*/
private Optional<Description> flagViolation(
MethodInvocationTree tree,
Matcher<ExpressionTree> matcher,
int formatStringParam,
String formatSpecifier,
VisitorState state) {
if (!matcher.matches(tree, state)) {
/* The invoked method is not targeted by this check. */
return Optional.empty();
}
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() > formatStringParam + 1) {
/*
* This method invocation uses explicit string concatenation but _also_ already relies on
* format specifiers: flag but don't suggest a fix.
*/
return Optional.of(describeMatch(tree));
}
ExpressionTree formatStringArg = arguments.get(formatStringParam);
ReplacementArgumentsConstructor replacementConstructor =
new ReplacementArgumentsConstructor(formatSpecifier);
formatStringArg.accept(replacementConstructor, state);
return Optional.of(
describeMatch(
tree,
SuggestedFix.replace(
formatStringArg, replacementConstructor.getReplacementArguments(state))));
}
private static boolean hasNonConstantStringConcatenationArgument(
MethodInvocationTree tree, int argPosition, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() <= argPosition) {
/* This method doesn't accept enough parameters. */
return false;
}
ExpressionTree argument = ASTHelpers.stripParentheses(arguments.get(argPosition));
return argument instanceof BinaryTree
&& isStringTyped(argument, state)
&& ASTHelpers.constValue(argument, String.class) == null;
}
private static boolean isStringTyped(ExpressionTree tree, VisitorState state) {
return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state);
}
private static class ReplacementArgumentsConstructor
extends SimpleTreeVisitor<Void, VisitorState> {
private final StringBuilder formatString = new StringBuilder();
private final List<Tree> formatArguments = new ArrayList<>();
private final String formatSpecifier;
ReplacementArgumentsConstructor(String formatSpecifier) {
this.formatSpecifier = formatSpecifier;
}
@Nullable
@Override
public Void visitBinary(BinaryTree tree, VisitorState state) {
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
tree.getLeftOperand().accept(this, state);
tree.getRightOperand().accept(this, state);
} else {
appendExpression(tree);
}
return null;
}
@Nullable
@Override
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
return tree.getExpression().accept(this, state);
}
@Nullable
@Override
protected Void defaultAction(Tree tree, VisitorState state) {
appendExpression(tree);
return null;
}
private void appendExpression(Tree tree) {
if (tree instanceof LiteralTree) {
formatString.append(((LiteralTree) tree).getValue());
} else {
formatString.append(formatSpecifier);
formatArguments.add(tree);
}
}
private String getReplacementArguments(VisitorState state) {
return state.getConstantExpression(formatString.toString())
+ ", "
+ formatArguments.stream()
.map(tree -> SourceCode.treeToString(tree, state))
.collect(joining(", "));
}
}
}

View File

@@ -0,0 +1,112 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.TypesWithUndefinedEquality;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags redundant identity conversions. */
// XXX: Consider detecting cases where a flagged expression is passed to a method, and where removal
// of the identify conversion would cause a different method overload to be selected. Depending on
// the target method such a modification may change the code's semantics or performance.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid or clarify identity conversions",
link = BUG_PATTERNS_BASE_URL + "IdentityConversion",
linkType = CUSTOM,
severity = WARNING,
tags = SIMPLIFICATION)
public final class IdentityConversion extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableListMultimap",
"com.google.common.collect.ImmutableMap",
"com.google.common.collect.ImmutableMultimap",
"com.google.common.collect.ImmutableMultiset",
"com.google.common.collect.ImmutableRangeMap",
"com.google.common.collect.ImmutableRangeSet",
"com.google.common.collect.ImmutableSet",
"com.google.common.collect.ImmutableSetMultimap",
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
staticMethod()
.onClass("reactor.core.publisher.Flux")
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() != 1 || !IS_CONVERSION_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree sourceTree = arguments.get(0);
Type sourceType = ASTHelpers.getType(sourceTree);
Type resultType = ASTHelpers.getType(tree);
TargetType targetType = ASTHelpers.targetType(state);
if (sourceType == null || resultType == null || targetType == null) {
return Description.NO_MATCH;
}
if (!state.getTypes().isSameType(sourceType, resultType)
&& !isConvertibleWithWellDefinedEquality(sourceType, targetType.type(), state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
"This method invocation appears redundant; remove it or suppress this warning and "
+ "add a comment explaining its purpose")
.addFix(SuggestedFix.replace(tree, SourceCode.treeToString(sourceTree, state)))
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
.build();
}
private static boolean isConvertibleWithWellDefinedEquality(
Type sourceType, Type targetType, VisitorState state) {
Types types = state.getTypes();
return !types.isSameType(targetType, OBJECT_TYPE.get(state))
&& types.isConvertible(sourceType, targetType)
&& Arrays.stream(TypesWithUndefinedEquality.values())
.noneMatch(b -> b.matchesType(sourceType, state) || b.matchesType(targetType, state));
}
}

View File

@@ -0,0 +1,83 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.methodReturns;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodTree;
import java.util.SortedSet;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} that flags {@link SortedSet} property declarations inside
* {@code @Value.Immutable}- and {@code @Value.Modifiable}-annotated types that lack a
* {@code @Value.NaturalOrder} or {@code @Value.ReverseOrder} annotation.
*
* <p>Without such an annotation:
*
* <ul>
* <li>deserialization of the enclosing type requires that the associated JSON property is
* present, contrary to the way in which Immutables handles other collection properties; and
* <li>different instances may use different comparator implementations (e.g. deserialization
* would default to natural order sorting), potentially leading to subtle bugs.
* </ul>
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be "
+ "annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`",
link = BUG_PATTERNS_BASE_URL + "ImmutablesSortedSetComparator",
linkType = CUSTOM,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ImmutablesSortedSetComparator extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodTree> METHOD_LACKS_ANNOTATION =
allOf(
methodReturns(isSubtypeOf(SortedSet.class)),
anyOf(
allOf(
hasModifier(Modifier.ABSTRACT),
enclosingClass(
anyOf(
hasAnnotation("org.immutables.value.Value.Immutable"),
hasAnnotation("org.immutables.value.Value.Modifiable")))),
hasAnnotation("org.immutables.value.Value.Default")),
not(
anyOf(
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder builder = SuggestedFix.builder();
String valueTypeIdentifier =
SuggestedFixes.qualifyType(state, builder, "org.immutables.value.Value");
return describeMatch(
tree,
builder.prefixWith(tree, String.format("@%s.NaturalOrder ", valueTypeIdentifier)).build());
}
}

View File

@@ -0,0 +1,202 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check that enforces that test classes:
// 1. Are named `*Test` or `Abstract*TestCase`.
// 2. If not `abstract`, are package-private and don't have public methods and subclasses.
// 3. Only have private fields.
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
@AutoService(BugChecker.class)
@BugPattern(
summary = "JUnit method declaration can likely be improved",
link = BUG_PATTERNS_BASE_URL + "JUnitMethodDeclaration",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class JUnitMethodDeclaration extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TEST_PREFIX = "test";
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final Matcher<MethodTree> HAS_UNMODIFIABLE_SIGNATURE =
anyOf(
annotations(AT_LEAST_ONE, isType("java.lang.Override")),
allOf(
Matchers.not(hasModifier(Modifier.FINAL)),
Matchers.not(hasModifier(Modifier.PRIVATE)),
enclosingClass(hasModifier(Modifier.ABSTRACT))));
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
private static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (HAS_UNMODIFIABLE_SIGNATURE.matches(tree, state)) {
return Description.NO_MATCH;
}
boolean isTestMethod = TEST_METHOD.matches(tree, state);
if (!isTestMethod && !SETUP_OR_TEARDOWN_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(tree.getModifiers(), state, ILLEGAL_MODIFIERS)
.ifPresent(fixBuilder::merge);
if (isTestMethod) {
suggestTestMethodRenameIfApplicable(tree, fixBuilder, state);
}
return fixBuilder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fixBuilder.build());
}
private void suggestTestMethodRenameIfApplicable(
MethodTree tree, SuggestedFix.Builder fixBuilder, VisitorState state) {
tryCanonicalizeMethodName(tree)
.ifPresent(
newName ->
findMethodRenameBlocker(newName, state)
.ifPresentOrElse(
blocker -> reportMethodRenameBlocker(tree, blocker, state),
() -> fixBuilder.merge(SuggestedFixes.renameMethod(tree, newName, state))));
}
private void reportMethodRenameBlocker(MethodTree tree, String reason, VisitorState state) {
state.reportMatch(
buildDescription(tree)
.setMessage(
String.format(
"This method's name should not redundantly start with `%s` (but note that %s)",
TEST_PREFIX, reason))
.build());
}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration.
* <li>Whether the rename would cause a method in a superclass to be overridden.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
if (isMethodInEnclosingClass(methodName, state)) {
return Optional.of(
String.format("a method named `%s` already exists in this class", methodName));
}
if (isSimpleNameStaticallyImported(methodName, state)) {
return Optional.of(String.format("`%s` is already statically imported", methodName));
}
if (isReservedKeyword(methodName)) {
return Optional.of(String.format("`%s` is a reserved keyword", methodName));
}
return Optional.empty();
}
private static boolean isMethodInEnclosingClass(String methodName, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.map(MethodTree::getName)
.map(Name::toString)
.anyMatch(methodName::equals);
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
return Optional.of(ASTHelpers.getSymbol(tree).getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))
.filter(not(String::isEmpty))
.map(name -> Character.toLowerCase(name.charAt(0)) + name.substring(1))
.filter(name -> !Character.isDigit(name.charAt(0)));
}
// XXX: Move to a `MoreMatchers` utility class.
private static Matcher<AnnotationTree> hasMetaAnnotation(String annotationClassName) {
TypePredicate typePredicate = hasAnnotation(annotationClassName);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Move to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -1,114 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.function.Predicate.not;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol;
import java.util.Optional;
import javax.lang.model.element.Modifier;
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check which enforces that test classes:
// 1. Are named `*Test` or `Abstract*TestCase`.
// 2. If not `abstract`, don't have public methods and subclasses.
// 3. Only have private fields.
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
@AutoService(BugChecker.class)
@BugPattern(
name = "JUnitMethodDeclaration",
summary = "JUnit method declaration can likely be improved",
linkType = BugPattern.LinkType.NONE,
severity = BugPattern.SeverityLevel.SUGGESTION,
tags = BugPattern.StandardTags.SIMPLIFICATION)
public final class JUnitMethodDeclarationCheck extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TEST_PREFIX = "test";
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final MultiMatcher<MethodTree, AnnotationTree> IS_OVERRIDE_METHOD =
annotations(AT_LEAST_ONE, isType("java.lang.Override"));
private static final MultiMatcher<MethodTree, AnnotationTree> IS_TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
private static final MultiMatcher<MethodTree, AnnotationTree> IS_SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Perhaps we should also skip analysis of non-`private` non-`final` methods in abstract
// classes?
if (IS_OVERRIDE_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
boolean isTestMethod = IS_TEST_METHOD.matches(tree, state);
if (!isTestMethod && !IS_SETUP_OR_TEARDOWN_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder builder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(tree.getModifiers(), state, ILLEGAL_MODIFIERS)
.ifPresent(builder::merge);
if (isTestMethod) {
// XXX: In theory this rename could clash with an existing method or static import. In that
// case we should emit a warning without a suggested replacement.
tryCanonicalizeMethodName(tree, state).ifPresent(builder::merge);
}
return builder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, builder.build());
}
private static Optional<SuggestedFix> tryCanonicalizeMethodName(
MethodTree tree, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(sym -> sym.getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))
.filter(not(String::isEmpty))
.map(name -> Character.toLowerCase(name.charAt(0)) + name.substring(1))
.filter(name -> !Character.isDigit(name.charAt(0)))
.map(name -> SuggestedFixes.renameMethod(tree, name, state));
}
// XXX: Move to a `MoreMatchers` utility class.
private static Matcher<AnnotationTree> hasMetaAnnotation(String annotationClassName) {
TypePredicate typePredicate = hasAnnotation(annotationClassName);
return (tree, state) -> {
Symbol sym = ASTHelpers.getDeclaredSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Move to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -1,17 +1,18 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -32,28 +33,29 @@ import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags annotation array listings which aren't sorted lexicographically.
* A {@link BugChecker} that flags annotation array listings which aren't sorted lexicographically.
*
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
* resolution, and can even avoid it if two branches add the same entry.
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "LexicographicalAnnotationAttributeListing",
summary = "Where possible, sort annotation array attributes lexicographically",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.STYLE,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class LexicographicalAnnotationAttributeListingCheck extends BugChecker
link = BUG_PATTERNS_BASE_URL + "LexicographicalAnnotationAttributeListing",
linkType = CUSTOM,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationAttributeListing extends BugChecker
implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableSet<String> BLACKLISTED_ANNOTATIONS =
@@ -62,24 +64,27 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
"com.fasterxml.jackson.annotation.JsonPropertyOrder#value",
"io.swagger.annotations.ApiImplicitParams#value",
"io.swagger.v3.oas.annotations.Parameters#value",
"javax.xml.bind.annotation.XmlType#propOrder");
"javax.xml.bind.annotation.XmlType#propOrder",
"org.springframework.context.annotation.PropertySource#value",
"org.springframework.test.context.TestPropertySource#locations",
"org.springframework.test.context.TestPropertySource#value");
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListing:";
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
private final AnnotationAttributeMatcher matcher;
/** Instantiates the default {@link LexicographicalAnnotationAttributeListingCheck}. */
public LexicographicalAnnotationAttributeListingCheck() {
/** Instantiates the default {@link LexicographicalAnnotationAttributeListing}. */
public LexicographicalAnnotationAttributeListing() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link LexicographicalAnnotationAttributeListingCheck}.
* Instantiates a customized {@link LexicographicalAnnotationAttributeListing}.
*
* @param flags Any provided command line flags.
*/
public LexicographicalAnnotationAttributeListingCheck(ErrorProneFlags flags) {
public LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
matcher = createAnnotationAttributeMatcher(flags);
}
@@ -130,7 +135,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
/* The elements aren't sorted. Suggest the sorted alternative. */
String suggestion =
desiredOrdering.stream()
.map(expr -> Util.treeToString(expr, state))
.map(expr -> SourceCode.treeToString(expr, state))
.collect(joining(", ", "{", "}"));
return Optional.of(SuggestedFix.builder().replace(array, suggestion));
}
@@ -155,7 +160,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
// XXX: Perhaps we should use `Collator` with `.setStrength(Collator.PRIMARY)` and
// `getCollationKey`. Not clear whether that's worth the hassle at this point.
return ImmutableList.sortedCopyOf(
Comparator.comparing(
comparing(
e -> getStructure(e, state),
Comparators.lexicographical(
Comparators.lexicographical(
@@ -173,20 +178,23 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
new TreeScanner<Void, Void>() {
@Nullable
@Override
public Void visitIdentifier(IdentifierTree node, Void ctx) {
public Void visitIdentifier(IdentifierTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitIdentifier(node, ctx);
}
@Nullable
@Override
public Void visitLiteral(LiteralTree node, Void ctx) {
public Void visitLiteral(LiteralTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitLiteral(node, ctx);
}
@Nullable
@Override
public Void visitPrimitiveType(PrimitiveTypeTree node, Void ctx) {
public Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitPrimitiveType(node, ctx);
}
@@ -196,7 +204,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
* Tokens are split on `=` so that e.g. inline Spring property declarations are properly
* sorted by key, then value.
*/
return ImmutableList.copyOf(Util.treeToString(node, state).split("=", -1));
return ImmutableList.copyOf(SourceCode.treeToString(node, state).split("=", -1));
}
}.scan(array, null);

View File

@@ -0,0 +1,82 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static java.util.Comparator.comparing;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags annotations that are not lexicographically sorted.
*
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
* resolution, and can even avoid it if two branches add the same annotation.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Sort annotations lexicographically where possible",
link = BUG_PATTERNS_BASE_URL + "LexicographicalAnnotationListing",
linkType = CUSTOM,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationListing extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
List<? extends AnnotationTree> originalOrdering = tree.getModifiers().getAnnotations();
if (originalOrdering.size() < 2) {
return Description.NO_MATCH;
}
ImmutableList<? extends AnnotationTree> sortedAnnotations = sort(originalOrdering, state);
if (originalOrdering.equals(sortedAnnotations)) {
return Description.NO_MATCH;
}
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
Description.Builder description = buildDescription(originalOrdering.get(0));
fix.ifPresent(description::addFix);
return description.build();
}
private static ImmutableList<? extends AnnotationTree> sort(
List<? extends AnnotationTree> annotations, VisitorState state) {
return annotations.stream()
.sorted(comparing(annotation -> SourceCode.treeToString(annotation, state)))
.collect(toImmutableList());
}
private static Optional<Fix> tryFixOrdering(
List<? extends AnnotationTree> originalAnnotations,
ImmutableList<? extends AnnotationTree> sortedAnnotations,
VisitorState state) {
return Streams.zip(
originalAnnotations.stream(),
sortedAnnotations.stream(),
(original, replacement) ->
SuggestedFix.builder()
.replace(original, SourceCode.treeToString(replacement, state)))
.reduce(SuggestedFix.Builder::merge)
.map(SuggestedFix.Builder::build);
}
}

View File

@@ -1,15 +1,15 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
@@ -36,7 +36,7 @@ import java.util.Optional;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} which flags lambda expressions that can be replaced with method references.
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
*/
// XXX: Other custom expressions we could rewrite:
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
@@ -48,16 +48,16 @@ import javax.lang.model.element.Name;
// black-and-white. Maybe we can more closely approximate it?
// XXX: With Java 9's introduction of `Predicate.not`, we could write many lambda expressions to
// `not(some::reference)`.
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
@AutoService(BugChecker.class)
@BugPattern(
name = "MethodReferenceUsage",
summary = "Prefer method references over lambda expressions",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.STYLE,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class MethodReferenceUsageCheck extends BugChecker
implements LambdaExpressionTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "MethodReferenceUsage",
linkType = CUSTOM,
severity = SUGGESTION,
tags = STYLE)
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
@@ -69,7 +69,10 @@ public final class MethodReferenceUsageCheck extends BugChecker
*/
return constructMethodRef(tree, tree.getBody())
.map(SuggestedFix.Builder::build)
.filter(fix -> SuggestedFixes.compilesWithFix(fix, state))
.filter(
fix ->
SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
@@ -99,6 +102,8 @@ public final class MethodReferenceUsageCheck extends BugChecker
.flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0)));
}
// XXX: Replace nested `Optional` usage.
@SuppressWarnings("NestedOptionals")
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
return matchArguments(lambdaExpr, subTree)
@@ -155,6 +160,8 @@ public final class MethodReferenceUsageCheck extends BugChecker
return constructFix(lambdaExpr, lhsType.tsym, subTree.getIdentifier());
}
// XXX: Refactor or replace inner `Optional` with a custom type.
@SuppressWarnings("NestedOptionals")
private static Optional<Optional<Name>> matchArguments(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
ImmutableList<Name> expectedArguments = getVariables(lambdaExpr);

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
/** A {@link BugChecker} that flags likely missing Refaster annotations. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "The Refaster template contains a method without any Refaster annotations",
link = BUG_PATTERNS_BASE_URL + "MissingRefasterAnnotation",
linkType = CUSTOM,
severity = WARNING,
tags = LIKELY_ERROR)
public final class MissingRefasterAnnotation extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> REFASTER_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
isType("com.google.errorprone.refaster.annotation.Placeholder"),
isType("com.google.errorprone.refaster.annotation.BeforeTemplate"),
isType("com.google.errorprone.refaster.annotation.AfterTemplate")));
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
long methodTypes =
tree.getMembers().stream()
.filter(member -> member.getKind() == Tree.Kind.METHOD)
.map(MethodTree.class::cast)
.filter(method -> !ASTHelpers.isGeneratedConstructor(method))
.map(method -> REFASTER_ANNOTATION.matches(method, state))
.distinct()
.count();
return methodTypes < 2 ? Description.NO_MATCH : buildDescription(tree).build();
}
}

View File

@@ -0,0 +1,60 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags method invocations for which all arguments are wrapped using
* {@link org.mockito.Mockito#eq}; this is redundant.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Don't unnecessarily use Mockito's `eq(...)`",
link = BUG_PATTERNS_BASE_URL + "MockitoStubbing",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoStubbing extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.isEmpty() || !arguments.stream().allMatch(arg -> isEqInvocation(arg, state))) {
return Description.NO_MATCH;
}
SuggestedFix.Builder suggestedFix = SuggestedFix.builder();
for (ExpressionTree arg : arguments) {
suggestedFix.replace(
arg,
SourceCode.treeToString(
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
}
return describeMatch(tree, suggestedFix.build());
}
private static boolean isEqInvocation(ExpressionTree tree, VisitorState state) {
return tree instanceof MethodInvocationTree && MOCKITO_EQ_METHOD.matches(tree, state);
}
}

View File

@@ -0,0 +1,53 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.List;
import java.util.Optional;
/** A {@link BugChecker} that flags nesting of {@link Optional Optionals}. */
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Avoid nesting `Optional`s inside `Optional`s; the resultant code is hard to reason about",
link = BUG_PATTERNS_BASE_URL + "NestedOptionals",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
Type optionalType = OPTIONAL.get(state);
Type type = ASTHelpers.getType(tree);
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
return false;
}
List<Type> typeArguments = type.getTypeArguments();
return !typeArguments.isEmpty()
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
}
}

View File

@@ -0,0 +1,93 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.function.BiFunction;
import reactor.core.publisher.Mono;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags {@link Mono} operations that are known to be vacuous, given that
* they are invoked on a {@link Mono} that is known not to complete empty.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid vacuous operations on known non-empty `Mono`s",
link = BUG_PATTERNS_BASE_URL + "NonEmptyMono",
linkType = CUSTOM,
severity = WARNING,
tags = SIMPLIFICATION)
// XXX: This check does not simplify `someFlux.defaultIfEmpty(T).{defaultIfEmpty(T),hasElements()}`,
// as `someFlux.defaultIfEmpty(T)` yields a `Flux` rather than a `Mono`. Consider adding support for
// these cases.
// XXX: Given more advanced analysis many more expressions could be flagged. Consider
// `Mono.just(someValue)`, `Flux.just(someNonEmptySequence)`,
// `someMono.switchIfEmpty(someProvablyNonEmptyMono)` and many other variants.
// XXX: Consider implementing a similar check for `Publisher`s that are known to complete without
// emitting a value (e.g. `Mono.empty()`, `someFlux.then()`, ...), or known not to complete normally
// (`Mono.never()`, `someFlux.repeat()`, `Mono.error(...)`, ...). The latter category could
// potentially be split out further.
public final class NonEmptyMono extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("defaultIfEmpty", "single", "switchIfEmpty");
private static final Matcher<ExpressionTree> NON_EMPTY_MONO =
anyOf(
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf(
"all",
"any",
"collect",
"collectList",
"collectMap",
"collectMultimap",
"collectSortedList",
"count",
"elementAt",
"hasElement",
"hasElements",
"last",
"reduceWith",
"single"),
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.named("reduce")
.withParameters(Object.class.getName(), BiFunction.class.getName()),
instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!MONO_SIZE_CHECK.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
if (!NON_EMPTY_MONO.matches(receiver, state)) {
return Description.NO_MATCH;
}
return describeMatch(
tree, SuggestedFix.replace(tree, SourceCode.treeToString(receiver, state)));
}
}

View File

@@ -1,17 +1,17 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
@@ -31,28 +31,27 @@ import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code Comparator#comparing*} invocations that can be replaced
* A {@link BugChecker} that flags {@code Comparator#comparing*} invocations that can be replaced
* with an equivalent alternative so as to avoid unnecessary (un)boxing.
*/
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more
// specific types.
// XXX: Change this checker's name?
@AutoService(BugChecker.class)
@BugPattern(
name = "PrimitiveComparison",
summary =
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
+ " of the provided function",
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.PERFORMANCE,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class PrimitiveComparisonCheck extends BugChecker
implements MethodInvocationTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "PrimitiveComparison",
linkType = CUSTOM,
severity = WARNING,
tags = PERFORMANCE)
public final class PrimitiveComparison extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> STATIC_COMPARISION_METHOD =
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
anyOf(
staticMethod()
.onClass(Comparator.class.getName())
@@ -61,7 +60,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
.onClass(Comparator.class.getName())
.named("comparing")
.withParameters(Function.class.getName()));
private static final Matcher<ExpressionTree> INSTANCE_COMPARISION_METHOD =
private static final Matcher<ExpressionTree> INSTANCE_COMPARISON_METHOD =
anyOf(
instanceMethod()
.onDescendantOf(Comparator.class.getName())
@@ -73,29 +72,58 @@ public final class PrimitiveComparisonCheck extends BugChecker
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isStatic = STATIC_COMPARISION_METHOD.matches(tree, state);
if (!isStatic && !INSTANCE_COMPARISION_METHOD.matches(tree, state)) {
boolean isStatic = STATIC_COMPARISON_METHOD.matches(tree, state);
if (!isStatic && !INSTANCE_COMPARISON_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
return getPotentiallyBoxedReturnType(tree.getArguments().get(0))
.flatMap(cmpType -> tryFix(tree, state, cmpType, isStatic))
.flatMap(cmpType -> attemptMethodInvocationReplacement(tree, cmpType, isStatic, state))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional<Fix> tryFix(
MethodInvocationTree tree, VisitorState state, Type cmpType, boolean isStatic) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(methodSymbol -> methodSymbol.getSimpleName().toString())
.flatMap(
actualMethodName ->
Optional.of(getPreferredMethod(state, cmpType, isStatic))
.filter(not(actualMethodName::equals)))
.map(preferredMethodName -> suggestFix(tree, preferredMethodName, state));
private static Optional<Fix> attemptMethodInvocationReplacement(
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
String preferredMethodName = getPreferredMethod(cmpType, isStatic, state);
if (actualMethodName.equals(preferredMethodName)) {
return Optional.empty();
}
return Optional.of(
suggestFix(
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state));
}
private static String getPreferredMethod(VisitorState state, Type cmpType, boolean isStatic) {
/**
* Prefixes the given method name with generic type parameters if it replaces a {@code
* Comparator#comparing{,Double,Long,Int}} method which also has generic type parameters.
*
* <p>Such type parameters are retained as they are likely required.
*
* <p>Note that any type parameter to {@code Comparator#thenComparing} is likely redundant, and in
* any case becomes obsolete once that method is replaced with {@code
* Comparator#thenComparing{Double,Long,Int}}. Conversion in the opposite direction does not
* require the introduction of a generic type parameter.
*/
private static String prefixTypeArgumentsIfRelevant(
String preferredMethodName, MethodInvocationTree tree, Type cmpType, VisitorState state) {
if (tree.getTypeArguments().isEmpty() || preferredMethodName.startsWith("then")) {
return preferredMethodName;
}
String typeArguments =
Stream.concat(
Stream.of(SourceCode.treeToString(tree.getTypeArguments().get(0), state)),
Stream.of(cmpType.tsym.getSimpleName())
.filter(u -> "comparing".equals(preferredMethodName)))
.collect(joining(", ", "<", ">"));
return typeArguments + preferredMethodName;
}
private static String getPreferredMethod(Type cmpType, boolean isStatic, VisitorState state) {
Types types = state.getTypes();
Symtab symtab = state.getSymtab();
@@ -130,9 +158,6 @@ public final class PrimitiveComparisonCheck extends BugChecker
}
}
// XXX: We drop explicitly specified generic type information. In case the number of type
// arguments before and after doesn't match, that's for the better. But if we e.g. replace
// `comparingLong` with `comparingInt`, then we should retain it.
private static Fix suggestFix(
MethodInvocationTree tree, String preferredMethodName, VisitorState state) {
ExpressionTree expr = tree.getMethodSelect();
@@ -145,7 +170,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
case MEMBER_SELECT:
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
return SuggestedFix.replace(
ms, Util.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
ms, SourceCode.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
default:
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
}

View File

@@ -1,21 +1,26 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.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.isNonNull;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -25,6 +30,7 @@ import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
@@ -42,17 +48,19 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags redundant explicit string conversions. */
/** A {@link BugChecker} that flags redundant explicit string conversions. */
@AutoService(BugChecker.class)
@BugPattern(
name = "RedundantStringConversion",
summary = "Avoid redundant string conversions when possible",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class RedundantStringConversionCheck extends BugChecker
link = BUG_PATTERNS_BASE_URL + "RedundantStringConversion",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RedundantStringConversion extends BugChecker
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String FLAG_PREFIX = "RedundantStringConversion:";
@@ -66,52 +74,34 @@ public final class RedundantStringConversionCheck extends BugChecker
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> STRING = isSameType(String.class);
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
private static final Matcher<ExpressionTree> NON_NULL_STRING = allOf(STRING, isNonNull());
private static final Matcher<ExpressionTree> NON_NULL_STRING =
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 +117,10 @@ public final class RedundantStringConversionCheck extends BugChecker
staticMethod().onClass(String.class.getName()).named("format"),
instanceMethod().onDescendantOf(Formatter.class.getName()).named("format"),
instanceMethod()
.onDescendantOf(PrintStream.class.getName())
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
.namedAnyOf("format", "printf"),
instanceMethod()
.onDescendantOf(PrintStream.class.getName())
.namedAnyOf("print", "println")
.withParameters(Object.class.getName()),
instanceMethod()
.onDescendantOf(PrintWriter.class.getName())
.namedAnyOf("format", "printf"),
instanceMethod()
.onDescendantOf(PrintWriter.class.getName())
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
.namedAnyOf("print", "println")
.withParameters(Object.class.getName()),
staticMethod()
@@ -156,19 +139,19 @@ public final class RedundantStringConversionCheck extends BugChecker
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("trace", "debug", "info", "warn", "error");
private final Matcher<ExpressionTree> conversionMethodMatcher;
private final Matcher<MethodInvocationTree> conversionMethodMatcher;
/** Instantiates the default {@link RedundantStringConversionCheck}. */
public RedundantStringConversionCheck() {
/** Instantiates the default {@link RedundantStringConversion}. */
public RedundantStringConversion() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link RedundantStringConversionCheck}.
* Instantiates a customized {@link RedundantStringConversion}.
*
* @param flags Any provided command line flags.
*/
public RedundantStringConversionCheck(ErrorProneFlags flags) {
public RedundantStringConversion(ErrorProneFlags flags) {
conversionMethodMatcher = createConversionMethodMatcher(flags);
}
@@ -186,7 +169,7 @@ public final class RedundantStringConversionCheck extends BugChecker
List<SuggestedFix.Builder> fixes = new ArrayList<>();
// XXX: Not so nice: we try to simplify the RHS twice.
// XXX: Avoid trying to simplify the RHS twice.
ExpressionTree preferredRhs = trySimplify(rhs, state).orElse(rhs);
if (STRING.matches(preferredRhs, state)) {
tryFix(lhs, state, ANY_EXPR).ifPresent(fixes::add);
@@ -243,7 +226,7 @@ public final class RedundantStringConversionCheck extends BugChecker
.flatMap(args -> tryFix(args.get(index), state, ANY_EXPR));
}
// XXX: Write another check which checks that Formatter patterns don't use `{}` and have a
// XXX: Write another check that checks that Formatter patterns don't use `{}` and have a
// matching number of arguments of the appropriate type. Also flag explicit conversions from
// `Formattable` to string.
private Optional<SuggestedFix.Builder> tryFixFormatter(
@@ -274,17 +257,17 @@ public final class RedundantStringConversionCheck extends BugChecker
return tryFixFormatterArguments(arguments, state, ANY_EXPR, ANY_EXPR);
}
// XXX: Write another check which checks that SLF4J patterns don't use `%s` and have a matching
// XXX: Write another check that checks that SLF4J patterns don't use `%s` and have a matching
// number of arguments of the appropriate type. Also flag explicit conversions from `Throwable` to
// string as the last logger argument. Suggests either dropping the converison or going with
// string as the last logger argument. Suggests either dropping the conversion or going with
// `Throwable#getMessage()` instead.
private Optional<SuggestedFix.Builder> tryFixSlf4jLogger(
List<? extends ExpressionTree> arguments, VisitorState state) {
/*
* SLF4J treats the final argument to a log statement specially if it is a `Throwabe`: it
* SLF4J treats the final argument to a log statement specially if it is a `Throwable`: it
* will always choose to render the associated stacktrace, even if the argument has a
* matching `{}` placeholder. (In this case the `{}` will simply be logged verbatim.) So if
* a log statement's final argument is the string representation of a `Throwble`, then we
* a log statement's final argument is the string representation of a `Throwable`, then we
* must not strip this explicit string conversion, as that would change the statement's
* semantics.
*/
@@ -327,7 +310,7 @@ public final class RedundantStringConversionCheck extends BugChecker
return trySimplify(tree, state, filter)
.map(
replacement ->
SuggestedFix.builder().replace(tree, Util.treeToString(replacement, state)));
SuggestedFix.builder().replace(tree, SourceCode.treeToString(replacement, state)));
}
private Optional<ExpressionTree> trySimplify(
@@ -338,11 +321,15 @@ public final class RedundantStringConversionCheck extends BugChecker
}
private Optional<ExpressionTree> trySimplify(ExpressionTree tree, VisitorState state) {
if (tree.getKind() != Kind.METHOD_INVOCATION || !conversionMethodMatcher.matches(tree, state)) {
if (tree.getKind() != Kind.METHOD_INVOCATION) {
return Optional.empty();
}
MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
if (!conversionMethodMatcher.matches(methodInvocation, state)) {
return Optional.empty();
}
switch (methodInvocation.getArguments().size()) {
case 0:
return trySimplifyNullaryMethod(methodInvocation, state);
@@ -351,7 +338,7 @@ public final class RedundantStringConversionCheck extends BugChecker
default:
throw new IllegalStateException(
"Cannot simplify method call with two or more arguments: "
+ Util.treeToString(tree, state));
+ SourceCode.treeToString(tree, state));
}
}
@@ -364,7 +351,7 @@ public final class RedundantStringConversionCheck extends BugChecker
return Optional.of(methodInvocation.getMethodSelect())
.filter(methodSelect -> methodSelect.getKind() == Kind.MEMBER_SELECT)
.map(methodSelect -> ((MemberSelectTree) methodSelect).getExpression())
.filter(expr -> !"super".equals(Util.treeToString(expr, state)));
.filter(expr -> !"super".equals(SourceCode.treeToString(expr, state)));
}
private static Optional<ExpressionTree> trySimplifyUnaryMethod(
@@ -383,7 +370,8 @@ public final class RedundantStringConversionCheck extends BugChecker
.orElse(Description.NO_MATCH);
}
private static Matcher<ExpressionTree> createConversionMethodMatcher(ErrorProneFlags flags) {
private static Matcher<MethodInvocationTree> createConversionMethodMatcher(
ErrorProneFlags flags) {
// XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas.
// For this class methods accepting more than one argument are not valid, but still: not nice.
return flags

View File

@@ -0,0 +1,59 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.Refaster;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags unnecessary {@link Refaster#anyOf(Object[])} usages.
*
* <p>Note that this logic can't be implemented as a Refaster template, as the {@link Refaster}
* class is treated specially.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "`Refaster#anyOf` should be passed at least two parameters",
link = BUG_PATTERNS_BASE_URL + "RefasterAnyOfUsage",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (REFASTER_ANY_OF.matches(tree, state)) {
switch (tree.getArguments().size()) {
case 0:
// We can't safely fix this case; dropping the expression may produce non-compilable code.
return describeMatch(tree);
case 1:
return describeMatch(
tree,
SuggestedFix.replace(
tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
default:
/* Handled below. */
}
}
return Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,113 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.EnumSet;
import java.util.Set;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} which suggests a canonical set of modifiers for Refaster class and method
* definitions.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Refaster class and method definitions should specify a canonical set of modifiers",
link = BUG_PATTERNS_BASE_URL + "RefasterTemplateModifiers",
linkType = CUSTOM,
severity = SUGGESTION,
tags = STYLE)
public final class RefasterTemplateModifiers extends BugChecker
implements ClassTreeMatcher, MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<Tree> BEFORE_TEMPLATE_METHOD = hasAnnotation(BeforeTemplate.class);
private static final Matcher<Tree> AFTER_TEMPLATE_METHOD = hasAnnotation(AfterTemplate.class);
private static final Matcher<Tree> PLACEHOLDER_METHOD = hasAnnotation(Placeholder.class);
private static final Matcher<Tree> REFASTER_METHOD =
anyOf(BEFORE_TEMPLATE_METHOD, AFTER_TEMPLATE_METHOD, PLACEHOLDER_METHOD);
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
if (!hasMatchingMember(tree, BEFORE_TEMPLATE_METHOD, state)) {
/* This class does not contain a Refaster template. */
return Description.NO_MATCH;
}
SuggestedFix fix = suggestCanonicalModifiers(tree, state);
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix);
}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!REFASTER_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
return SuggestedFixes.removeModifiers(
tree,
state,
Modifier.FINAL,
Modifier.PRIVATE,
Modifier.PROTECTED,
Modifier.PUBLIC,
Modifier.STATIC,
Modifier.SYNCHRONIZED)
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static SuggestedFix suggestCanonicalModifiers(ClassTree tree, VisitorState state) {
Set<Modifier> modifiersToAdd = EnumSet.noneOf(Modifier.class);
Set<Modifier> modifiersToRemove =
EnumSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC, Modifier.SYNCHRONIZED);
if (!hasMatchingMember(tree, PLACEHOLDER_METHOD, state)) {
/*
* Templates without a `@Placeholder` method should be `final`. Note that Refaster enforces
* that `@Placeholder` methods are `abstract`, so templates _with_ such a method will
* naturally be `abstract` and non-`final`.
*/
modifiersToAdd.add(Modifier.FINAL);
modifiersToRemove.add(Modifier.ABSTRACT);
}
if (ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class) != null) {
/* Nested classes should be `static`. */
modifiersToAdd.add(Modifier.STATIC);
}
SuggestedFix.Builder fix = SuggestedFix.builder();
SuggestedFixes.addModifiers(tree, tree.getModifiers(), state, modifiersToAdd)
.ifPresent(fix::merge);
SuggestedFixes.removeModifiers(tree.getModifiers(), state, modifiersToRemove)
.ifPresent(fix::merge);
return fix.build();
}
private static boolean hasMatchingMember(
ClassTree tree, Matcher<Tree> matcher, VisitorState state) {
return tree.getMembers().stream().anyMatch(member -> matcher.matches(member, state));
}
}

View File

@@ -0,0 +1,100 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
/**
* A {@link BugChecker} that flags {@code @RequestMapping} methods that have one or more parameters
* that appear to lack a relevant annotation.
*
* <p>Matched mappings are {@code @{Delete,Get,Patch,Post,Put,Request}Mapping}.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Make sure all `@RequestMapping` method parameters are annotated",
link = BUG_PATTERNS_BASE_URL + "RequestMappingAnnotation",
linkType = CUSTOM,
severity = WARNING,
tags = LIKELY_ERROR)
public final class RequestMappingAnnotation extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
// XXX: Generalize this logic to fully support Spring meta-annotations, then update the class
// documentation.
private static final Matcher<Tree> HAS_MAPPING_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "DeleteMapping"),
isType(ANN_PACKAGE_PREFIX + "GetMapping"),
isType(ANN_PACKAGE_PREFIX + "PatchMapping"),
isType(ANN_PACKAGE_PREFIX + "PostMapping"),
isType(ANN_PACKAGE_PREFIX + "PutMapping"),
isType(ANN_PACKAGE_PREFIX + "RequestMapping")));
// XXX: Add other parameters as necessary. Also consider whether it makes sense to have WebMVC-
// and WebFlux-specific logic. See
// https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
// and
// https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-arguments.
private static final Matcher<MethodTree> LACKS_PARAMETER_ANNOTATION =
not(
methodHasParameters(
ALL,
anyOf(
annotations(
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "PathVariable"),
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
isType(ANN_PACKAGE_PREFIX + "RequestPart"))),
isSameType("java.io.InputStream"),
isSameType("java.time.ZoneId"),
isSameType("java.util.Locale"),
isSameType("java.util.TimeZone"),
isSameType("javax.servlet.http.HttpServletRequest"),
isSameType("javax.servlet.http.HttpServletResponse"),
isSameType("org.springframework.http.HttpMethod"),
isSameType("org.springframework.web.context.request.NativeWebRequest"),
isSameType("org.springframework.web.context.request.WebRequest"),
isSameType("org.springframework.web.server.ServerWebExchange"),
isSameType("org.springframework.web.util.UriBuilder"),
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Auto-add `@RequestParam` where applicable.
// XXX: What about the `PurchasingProposerRequestParams` in POM? Implies `@RequestBody`?
// (Documentation doesn't mention this, IIUC.)
return HAS_MAPPING_ANNOTATION.matches(tree, state)
&& LACKS_PARAMETER_ANNOTATION.matches(tree, state)
? buildDescription(tree)
.setMessage(
"Not all parameters of this request mapping method are annotated; this may be a mistake. "
+ "If the unannotated parameters represent query string parameters, annotate them with `@RequestParam`.")
.build()
: Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,46 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.VariableTree;
/** A {@link BugChecker} that flags {@code @RequestParam} parameters with an unsupported type. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
link = BUG_PATTERNS_BASE_URL + "RequestParamType",
linkType = CUSTOM,
severity = ERROR,
tags = LIKELY_ERROR)
public final class RequestParamType extends BugChecker implements VariableTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<VariableTree> HAS_UNSUPPORTED_REQUEST_PARAM =
allOf(
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)));
@Override
public Description matchVariable(VariableTree tree, VisitorState state) {
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)
? describeMatch(tree)
: Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,89 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.isType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
/**
* A {@link BugChecker} that flags methods with Spring's {@code @Scheduled} annotation that lack New
* Relic Agent's {@code @Trace(dispatcher = true)}.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Scheduled operation must start a new New Relic transaction",
link = BUG_PATTERNS_BASE_URL + "ScheduledTransactionTrace",
linkType = CUSTOM,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ScheduledTransactionTrace extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TRACE_ANNOTATION_FQCN = "com.newrelic.api.agent.Trace";
private static final Matcher<Tree> IS_SCHEDULED =
hasAnnotation("org.springframework.scheduling.annotation.Scheduled");
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_SCHEDULED.matches(tree, state)) {
return Description.NO_MATCH;
}
ImmutableList<AnnotationTree> traceAnnotations =
TRACE_ANNOTATION.multiMatchResult(tree, state).matchingNodes();
if (traceAnnotations.isEmpty()) {
/* This method completely lacks the `@Trace` annotation; add it. */
return describeMatch(
tree,
SuggestedFix.builder()
.addImport(TRACE_ANNOTATION_FQCN)
.prefixWith(tree, "@Trace(dispatcher = true)")
.build());
}
AnnotationTree traceAnnotation = Iterables.getOnlyElement(traceAnnotations);
if (isCorrectAnnotation(traceAnnotation)) {
return Description.NO_MATCH;
}
/*
* The `@Trace` annotation is present but does not specify `dispatcher = true`. Add or update
* the `dispatcher` annotation element.
*/
return describeMatch(
traceAnnotation,
SuggestedFixes.updateAnnotationArgumentValues(
traceAnnotation, state, "dispatcher", ImmutableList.of("true"))
.build());
}
private static boolean isCorrectAnnotation(AnnotationTree traceAnnotation) {
return Boolean.TRUE.equals(
AnnotationMirrors.getAnnotationValue(
ASTHelpers.getAnnotationMirror(traceAnnotation), "dispatcher")
.getValue());
}
}

View File

@@ -1,16 +1,16 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
@@ -23,8 +23,9 @@ import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree.Kind;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags SLF4J usages that are likely to be in error. */
/** A {@link BugChecker} that flags SLF4J usages that are likely to be in error. */
// XXX: The special-casing of Throwable applies only to SLF4J 1.6.0+; see
// https://www.slf4j.org/faq.html#paramException. That should be documented.
// XXX: Also simplify `LOG.error(String.format("Something %s", arg), throwable)`.
@@ -32,14 +33,12 @@ import java.util.Optional;
// preconditions, ...
@AutoService(BugChecker.class)
@BugPattern(
name = "Slf4jLogStatement",
summary = "Make sure SLF4J log statements contain proper placeholders with matching arguments",
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.LIKELY_ERROR,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class Slf4jLogStatementCheck extends BugChecker
implements MethodInvocationTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "Slf4jLogStatement",
linkType = CUSTOM,
severity = WARNING,
tags = LIKELY_ERROR)
public final class Slf4jLogStatement extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
@@ -116,7 +115,7 @@ public final class Slf4jLogStatementCheck extends BugChecker
* replaced at this usage site.
*/
description.addFix(
SuggestedFix.replace(tree, Util.treeToString(tree, state).replace("%s", "{}")));
SuggestedFix.replace(tree, SourceCode.treeToString(tree, state).replace("%s", "{}")));
}
return false;

View File

@@ -1,18 +1,18 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
@@ -26,21 +26,22 @@ import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree.Kind;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code @RequestMapping} annotations that can be written more
* A {@link BugChecker} that flags {@code @RequestMapping} annotations that can be written more
* concisely.
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "SpringMvcAnnotation",
summary =
"Prefer the conciseness of `@{Get,Put,Post,Delete,Patch}Mapping` over `@RequestMapping`",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class SpringMvcAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
link = BUG_PATTERNS_BASE_URL + "SpringMvcAnnotation",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class SpringMvcAnnotation extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
@@ -94,7 +95,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
private static String extractMethod(ExpressionTree expr, VisitorState state) {
switch (expr.getKind()) {
case IDENTIFIER:
return Util.treeToString(expr, state);
return SourceCode.treeToString(expr, state);
case MEMBER_SELECT:
return ((MemberSelectTree) expr).getIdentifier().toString();
default:
@@ -107,7 +108,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
String newArguments =
tree.getArguments().stream()
.filter(not(argToRemove::equals))
.map(arg -> Util.treeToString(arg, state))
.map(arg -> SourceCode.treeToString(arg, state))
.collect(joining(", "));
return SuggestedFix.builder()

View File

@@ -1,13 +1,16 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
@@ -17,20 +20,22 @@ import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import java.util.Objects;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
/** A {@link BugChecker} which flags methods that can and should be statically imported. */
/**
* A {@link BugChecker} that flags methods and constants that can and should be statically imported.
*/
// XXX: Tricky cases:
// - `org.springframework.http.MediaType` (do except for `ALL`?)
// - `org.springframework.http.HttpStatus` (not always an improvement, and `valueOf` must
// certainly be excluded)
// - `com.google.common.collect.Tables`
// - `ch.qos.logback.classic.Level.{DEBUG, ERROR, INFO, TRACE, WARN"}`
// XXX: Also introduce a check which disallows static imports of certain methods. Candidates:
// XXX: Also introduce a check that disallows static imports of certain methods. Candidates:
// - `com.google.common.base.Strings`
// - `java.util.Optional.empty`
// - `java.util.Locale.ROOT`
@@ -40,22 +45,30 @@ import java.util.Optional;
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
@AutoService(BugChecker.class)
@BugPattern(
name = "StaticImport",
summary = "Method should be statically imported",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION,
providesFix = BugPattern.ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class StaticImportCheck extends BugChecker implements MemberSelectTreeMatcher {
summary = "Identifier should be statically imported",
link = BUG_PATTERNS_BASE_URL + "StaticImport",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
private static final long serialVersionUID = 1L;
/**
* Types whose members should be statically imported, unless exempted by {@link
* #STATIC_IMPORT_EXEMPTED_MEMBERS} or {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS}.
*/
@VisibleForTesting
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_CLASSES =
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_TYPES =
ImmutableSet.of(
"com.google.common.base.Preconditions",
"com.google.common.base.Predicates",
"com.google.common.base.Verify",
"com.google.common.collect.MoreCollectors",
"com.google.errorprone.BugPattern.LinkType",
"com.google.errorprone.BugPattern.SeverityLevel",
"com.google.errorprone.BugPattern.StandardTags",
"com.google.errorprone.matchers.Matchers",
"com.google.errorprone.refaster.ImportPolicy",
"com.mongodb.client.model.Accumulators",
"com.mongodb.client.model.Aggregates",
"com.mongodb.client.model.Filters",
@@ -64,8 +77,10 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
"com.mongodb.client.model.Sorts",
"com.mongodb.client.model.Updates",
"java.nio.charset.StandardCharsets",
"java.util.Collections",
"java.util.Comparator",
"java.util.Map.Entry",
"java.util.regex.Pattern",
"java.util.stream.Collectors",
"org.assertj.core.api.Assertions",
"org.assertj.core.api.InstanceOfAssertFactories",
@@ -80,14 +95,17 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
"org.mockito.Answers",
"org.mockito.ArgumentMatchers",
"org.mockito.Mockito",
"org.springframework.boot.test.context.SpringBootTest.WebEnvironment",
"org.springframework.format.annotation.DateTimeFormat.ISO",
"org.springframework.http.HttpHeaders",
"org.springframework.http.HttpMethod",
"org.springframework.http.MediaType",
"org.testng.Assert",
"reactor.function.TupleUtils");
/** Type members that should be statically imported. */
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_METHODS =
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_MEMBERS =
ImmutableSetMultimap.<String, String>builder()
.putAll(
"com.google.common.collect.ImmutableListMultimap",
@@ -108,8 +126,10 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
.put("com.google.common.collect.ImmutableTable", "toImmutableTable")
.put("com.google.common.collect.Sets", "toImmutableEnumSet")
.put("com.google.common.base.Functions", "identity")
.put("java.time.ZoneOffset", "UTC")
.put("java.util.function.Function", "identity")
.put("java.util.function.Predicate", "not")
.put("java.util.UUID", "randomUUID")
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
.putAll(
"java.util.Objects",
@@ -122,9 +142,57 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
.putAll("com.google.common.collect.Comparators", "emptiesFirst", "emptiesLast")
.build();
/**
* Type members that should never be statically imported.
*
* <p>Identifiers listed by {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS} should be omitted from
* this collection.
*/
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
// method name that could be considered "too vague" or could conceivably mean something else in a
// specific context is left out.
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
ImmutableSetMultimap.<String, String>builder()
.put("com.mongodb.client.model.Filters", "empty")
.putAll(
"java.util.Collections",
"addAll",
"copy",
"fill",
"list",
"max",
"min",
"nCopies",
"rotate",
"sort",
"swap")
.putAll("java.util.regex.Pattern", "compile", "matches", "quote")
.put("org.springframework.http.MediaType", "ALL")
.build();
/**
* Identifiers that should never be statically imported.
*
* <p>This should be a superset of the identifiers flagged by {@link
* com.google.errorprone.bugpatterns.BadImport}.
*/
@VisibleForTesting
static final ImmutableSet<String> STATIC_IMPORT_EXEMPTED_IDENTIFIERS =
ImmutableSet.of(
"builder",
"create",
"copyOf",
"from",
"getDefaultInstance",
"INSTANCE",
"newBuilder",
"of",
"valueOf");
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
if (!isCandidate(state)) {
if (!isCandidateContext(state) || !isCandidate(tree)) {
return Description.NO_MATCH;
}
@@ -139,10 +207,9 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
.orElse(Description.NO_MATCH);
}
private static boolean isCandidate(VisitorState state) {
private static boolean isCandidateContext(VisitorState state) {
Tree parentTree =
Objects.requireNonNull(
state.getPath().getParentPath(), "MemberSelectTree lacks enclosing node")
requireNonNull(state.getPath().getParentPath(), "MemberSelectTree lacks enclosing node")
.getLeaf();
switch (parentTree.getKind()) {
case IMPORT:
@@ -155,6 +222,17 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
}
}
private static boolean isCandidate(MemberSelectTree tree) {
String identifier = tree.getIdentifier().toString();
if (STATIC_IMPORT_EXEMPTED_IDENTIFIERS.contains(identifier)) {
return false;
}
Type type = ASTHelpers.getType(tree.getExpression());
return type != null
&& !STATIC_IMPORT_EXEMPTED_MEMBERS.containsEntry(type.toString(), identifier);
}
private static Optional<String> getCandidateSimpleName(StaticImportInfo importInfo) {
String canonicalName = importInfo.canonicalName();
return importInfo
@@ -162,8 +240,8 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
.toJavaUtil()
.filter(
name ->
STATIC_IMPORT_CANDIDATE_CLASSES.contains(canonicalName)
|| STATIC_IMPORT_CANDIDATE_METHODS.containsEntry(canonicalName, name));
STATIC_IMPORT_CANDIDATE_TYPES.contains(canonicalName)
|| STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(canonicalName, name));
}
private static Optional<Fix> tryStaticImport(

View File

@@ -0,0 +1,71 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/** A {@link BugChecker} that flags illegal time-zone related operations. */
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone",
link = BUG_PATTERNS_BASE_URL + "TimeZoneUsage",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class TimeZoneUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> BANNED_TIME_METHOD =
anyOf(
allOf(
instanceMethod()
.onDescendantOf(Clock.class.getName())
.namedAnyOf("getZone", "withZone"),
not(enclosingClass(isSubtypeOf(Clock.class)))),
staticMethod()
.onClass(Clock.class.getName())
.namedAnyOf(
"system",
"systemDefaultZone",
"systemUTC",
"tickMillis",
"tickMinutes",
"tickSeconds"),
staticMethod()
.onClassAny(
LocalDate.class.getName(),
LocalDateTime.class.getName(),
LocalTime.class.getName())
.named("now"),
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return BANNED_TIME_METHOD.matches(tree, state)
? buildDescription(tree).build()
: Description.NO_MATCH;
}
}

View File

@@ -1,7 +1,4 @@
/** Picnic Error Prone Contrib checks. */
@CheckReturnValue
@ParametersAreNonnullByDefault
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.annotations.CheckReturnValue;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
@@ -28,7 +28,7 @@ import java.util.stream.Stream;
* <p>This class allows one to define a whitelist or blacklist of annotations or their attributes.
* Annotations are identified by their fully qualified name.
*/
final class AnnotationAttributeMatcher implements Serializable {
public final class AnnotationAttributeMatcher implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean complement;
@@ -59,7 +59,7 @@ final class AnnotationAttributeMatcher implements Serializable {
* @param exclusions The listed annotations or annotation attributes are not matched.
* @return A non-{@code null} {@link AnnotationAttributeMatcher}.
*/
static AnnotationAttributeMatcher create(
public static AnnotationAttributeMatcher create(
Optional<? extends List<String>> inclusions, Iterable<String> exclusions) {
Set<String> includedWholeTypes = new HashSet<>();
Set<String> excludedWholeTypes = new HashSet<>();
@@ -97,7 +97,13 @@ final class AnnotationAttributeMatcher implements Serializable {
}
}
Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
/**
* Returns the subset of arguments of the given {@link AnnotationTree} matched by this instance.
*
* @param tree The annotation AST node to be inspected.
* @return Any matching annotation arguments.
*/
public Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
Type type = ASTHelpers.getType(tree.getAnnotationType());
if (type == null) {
return Stream.empty();

View File

@@ -0,0 +1,9 @@
package tech.picnic.errorprone.bugpatterns.util;
/** Utility class providing documentation-related code. */
public final class Documentation {
/** The base URL at which Error Prone Support bug patterns are hosted. */
public static final String BUG_PATTERNS_BASE_URL = "https://error-prone.picnic.tech/bugpatterns/";
private Documentation() {}
}

View File

@@ -0,0 +1,129 @@
package tech.picnic.errorprone.bugpatterns.util;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
/** Utility class that can be used to identify reserved keywords of the Java language. */
public final class JavaKeywords {
/**
* List of all reserved keywords in the Java language.
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.9">JDK 17 JLS
* section 3.9: Keywords</a>
*/
private static final ImmutableSet<String> RESERVED_KEYWORDS =
ImmutableSet.of(
"_",
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while");
/**
* List of all contextual keywords in the Java language.
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.9">JDK 17 JLS
* section 3.9: Keywords</a>
*/
private static final ImmutableSet<String> CONTEXTUAL_KEYWORDS =
ImmutableSet.of(
"exports",
"module",
"non-sealed",
"open",
"opens",
"permits",
"provides",
"record",
"requires",
"sealed",
"to",
"transitive",
"uses",
"var",
"with",
"yield");
/** List of all keywords in the Java language. */
private static final ImmutableSet<String> ALL_KEYWORDS =
Sets.union(RESERVED_KEYWORDS, CONTEXTUAL_KEYWORDS).immutableCopy();
private JavaKeywords() {}
/**
* Tells whether the given string is a reserved keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved keyword in the Java language.
*/
public static boolean isReservedKeyword(String str) {
return RESERVED_KEYWORDS.contains(str);
}
/**
* Tells whether the given string is a contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a contextual keyword in the Java language.
*/
public static boolean isContextualKeyword(String str) {
return CONTEXTUAL_KEYWORDS.contains(str);
}
/**
* Tells whether the given string is a reserved or contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved or contextual keyword in the Java
* language.
*/
public static boolean isKeyword(String str) {
return ALL_KEYWORDS.contains(str);
}
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
@@ -15,13 +15,20 @@ import java.util.regex.Pattern;
/** A method invocation expression {@link Matcher} factory. */
// XXX: Document better. The expressions accepted here could also be defined using `MethodMatchers`.
// So explain why this class is still useful.
final class MethodMatcherFactory {
public final class MethodMatcherFactory {
private static final Splitter ARGUMENT_TYPE_SPLITTER =
Splitter.on(',').trimResults().omitEmptyStrings();
private static final Pattern METHOD_SIGNATURE =
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
Matcher<ExpressionTree> create(Collection<String> signatures) {
/**
* Creates a {@link Matcher} of methods with any of the given signatures.
*
* @param signatures The method signatures of interest.
* @return A new {@link Matcher} which accepts invocation expressions of any method identified by
* the given signatures.
*/
public Matcher<ExpressionTree> create(Collection<String> signatures) {
return anyOf(
signatures.stream()
.map(MethodMatcherFactory::createMethodMatcher)

View File

@@ -1,19 +1,26 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.Tree;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
* AST nodes.
*/
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
final class Util {
private Util() {}
public final class SourceCode {
private SourceCode() {}
/**
* Returns a string representation of the given {@link Tree}, preferring the original source code
* (if available) over its prettified representation.
*
* @param tree The AST node of interest.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
* found.
* @return A non-{@code null} string.
*/
static String treeToString(Tree tree, VisitorState state) {
public static String treeToString(Tree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}

View File

@@ -0,0 +1,4 @@
/** Auxiliary utilities for use by Error Prone checks. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
package tech.picnic.errorprone.bugpatterns.util;

View File

@@ -8,13 +8,22 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractBigDecimalAssert;
import org.assertj.core.api.BigDecimalAssert;
// XXX: If we add a rule which drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
// cases below can go.
/**
* Refaster templates related to AssertJ assertions over {@link BigDecimal}s.
*
* <p>Note that, contrary to collections of Refaster templates for other {@link
* org.assertj.core.api.NumberAssert} subtypes, these templates do not rewrite to/from {@link
* BigDecimalAssert#isEqualTo(Object)} and {@link BigDecimalAssert#isNotEqualTo(Object)}. This is
* because {@link BigDecimal#equals(Object)} considers not only the numeric value of compared
* instances, but also their scale. As a result various seemingly straightforward transformations
* would actually subtly change the assertion's semantics.
*/
final class AssertJBigDecimalTemplates {
private AssertJBigDecimalTemplates() {}
static final class AbstractBigDecimalAssertIsEqualTo {
static final class AbstractBigDecimalAssertIsEqualByComparingTo {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return Refaster.anyOf(
@@ -24,11 +33,11 @@ final class AssertJBigDecimalTemplates {
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return bigDecimalAssert.isEqualTo(n);
return bigDecimalAssert.isEqualByComparingTo(n);
}
}
static final class AbstractBigDecimalAssertIsNotEqualTo {
static final class AbstractBigDecimalAssertIsNotEqualByComparingTo {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return Refaster.anyOf(
@@ -38,52 +47,7 @@ final class AssertJBigDecimalTemplates {
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return bigDecimalAssert.isNotEqualTo(n);
}
}
static final class AbstractBigDecimalAssertIsZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isZero(),
bigDecimalAssert.isEqualTo(0L),
bigDecimalAssert.isEqualTo(BigDecimal.ZERO));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(0);
}
}
static final class AbstractBigDecimalAssertIsNotZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isNotZero(),
bigDecimalAssert.isNotEqualTo(0L),
bigDecimalAssert.isNotEqualTo(BigDecimal.ZERO));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isNotEqualTo(0);
}
}
static final class AbstractBigDecimalAssertIsOne {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isOne(),
bigDecimalAssert.isEqualTo(1L),
bigDecimalAssert.isEqualTo(BigDecimal.ONE));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(1);
return bigDecimalAssert.isNotEqualByComparingTo(n);
}
}
}

View File

@@ -9,7 +9,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.math.BigInteger;
import org.assertj.core.api.AbstractBigIntegerAssert;
// XXX: If we add a rule which drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
// XXX: If we add a rule that drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
// cases below can go.
final class AssertJBigIntegerTemplates {
private AssertJBigIntegerTemplates() {}

View File

@@ -1,8 +1,8 @@
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.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -59,7 +59,7 @@ final class AssertJBooleanTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractBooleanAssert<?> after(boolean b) {
return assertThat(b).isTrue();
}
@@ -88,7 +88,7 @@ final class AssertJBooleanTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractBooleanAssert<?> after(boolean b) {
return assertThat(b).isFalse();
}

View File

@@ -15,11 +15,7 @@ final class AssertJByteTemplates {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> byteAssert, byte n) {
return Refaster.anyOf(
byteAssert.isCloseTo(n, offset((byte) 0)),
byteAssert.isCloseTo(Byte.valueOf(n), offset((byte) 0)),
byteAssert.isCloseTo(n, withPercentage(0)),
byteAssert.isCloseTo(Byte.valueOf(n), withPercentage(0)),
byteAssert.isEqualTo(Byte.valueOf(n)));
byteAssert.isCloseTo(n, offset((byte) 0)), byteAssert.isCloseTo(n, withPercentage(0)));
}
@AfterTemplate
@@ -33,10 +29,7 @@ final class AssertJByteTemplates {
AbstractByteAssert<?> before(AbstractByteAssert<?> byteAssert, byte n) {
return Refaster.anyOf(
byteAssert.isNotCloseTo(n, offset((byte) 0)),
byteAssert.isNotCloseTo(Byte.valueOf(n), offset((byte) 0)),
byteAssert.isNotCloseTo(n, withPercentage(0)),
byteAssert.isNotCloseTo(Byte.valueOf(n), withPercentage(0)),
byteAssert.isNotEqualTo(Byte.valueOf(n)));
byteAssert.isNotCloseTo(n, withPercentage(0)));
}
@AfterTemplate

View File

@@ -1,8 +1,8 @@
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.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -21,7 +21,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(CharSequence charSequence) {
assertThat(charSequence).isEmpty();
}
@@ -36,7 +36,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(CharSequence charSequence) {
return assertThat(charSequence).isNotEmpty();
}
@@ -49,7 +49,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(CharSequence charSequence, int length) {
return assertThat(charSequence).hasSize(length);
}

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

@@ -36,11 +36,7 @@ final class AssertJDoubleTemplates {
@BeforeTemplate
AbstractDoubleAssert<?> before(AbstractDoubleAssert<?> doubleAssert, double n) {
return Refaster.anyOf(
doubleAssert.isCloseTo(n, offset(0.0)),
doubleAssert.isCloseTo(Double.valueOf(n), offset(0.0)),
doubleAssert.isCloseTo(n, withPercentage(0.0)),
doubleAssert.isCloseTo(Double.valueOf(n), withPercentage(0.0)),
doubleAssert.isEqualTo(Double.valueOf(n)));
doubleAssert.isCloseTo(n, offset(0.0)), doubleAssert.isCloseTo(n, withPercentage(0.0)));
}
@AfterTemplate
@@ -54,10 +50,7 @@ final class AssertJDoubleTemplates {
AbstractDoubleAssert<?> before(AbstractDoubleAssert<?> doubleAssert, double n) {
return Refaster.anyOf(
doubleAssert.isNotCloseTo(n, offset(0.0)),
doubleAssert.isNotCloseTo(Double.valueOf(n), offset(0.0)),
doubleAssert.isNotCloseTo(n, withPercentage(0.0)),
doubleAssert.isNotCloseTo(Double.valueOf(n), withPercentage(0.0)),
doubleAssert.isNotEqualTo(Double.valueOf(n)));
doubleAssert.isNotCloseTo(n, withPercentage(0.0)));
}
@AfterTemplate

View File

@@ -36,11 +36,7 @@ final class AssertJFloatTemplates {
@BeforeTemplate
AbstractFloatAssert<?> before(AbstractFloatAssert<?> floatAssert, float n) {
return Refaster.anyOf(
floatAssert.isCloseTo(n, offset(0F)),
floatAssert.isCloseTo(Float.valueOf(n), offset(0F)),
floatAssert.isCloseTo(n, withPercentage(0)),
floatAssert.isCloseTo(Float.valueOf(n), withPercentage(0)),
floatAssert.isEqualTo(Float.valueOf(n)));
floatAssert.isCloseTo(n, offset(0F)), floatAssert.isCloseTo(n, withPercentage(0)));
}
@AfterTemplate
@@ -53,11 +49,7 @@ final class AssertJFloatTemplates {
@BeforeTemplate
AbstractFloatAssert<?> before(AbstractFloatAssert<?> floatAssert, float n) {
return Refaster.anyOf(
floatAssert.isNotCloseTo(n, offset(0F)),
floatAssert.isNotCloseTo(Float.valueOf(n), offset(0F)),
floatAssert.isNotCloseTo(n, withPercentage(0)),
floatAssert.isNotCloseTo(Float.valueOf(n), withPercentage(0)),
floatAssert.isNotEqualTo(Float.valueOf(n)));
floatAssert.isNotCloseTo(n, offset(0F)), floatAssert.isNotCloseTo(n, withPercentage(0)));
}
@AfterTemplate

View File

@@ -15,11 +15,7 @@ final class AssertJIntegerTemplates {
@BeforeTemplate
AbstractIntegerAssert<?> before(AbstractIntegerAssert<?> intAssert, int n) {
return Refaster.anyOf(
intAssert.isCloseTo(n, offset(0)),
intAssert.isCloseTo(Integer.valueOf(n), offset(0)),
intAssert.isCloseTo(n, withPercentage(0)),
intAssert.isCloseTo(Integer.valueOf(n), withPercentage(0)),
intAssert.isEqualTo(Integer.valueOf(n)));
intAssert.isCloseTo(n, offset(0)), intAssert.isCloseTo(n, withPercentage(0)));
}
@AfterTemplate
@@ -32,11 +28,7 @@ final class AssertJIntegerTemplates {
@BeforeTemplate
AbstractIntegerAssert<?> before(AbstractIntegerAssert<?> intAssert, int n) {
return Refaster.anyOf(
intAssert.isNotCloseTo(n, offset(0)),
intAssert.isNotCloseTo(Integer.valueOf(n), offset(0)),
intAssert.isNotCloseTo(n, withPercentage(0)),
intAssert.isNotCloseTo(Integer.valueOf(n), withPercentage(0)),
intAssert.isNotEqualTo(Integer.valueOf(n)));
intAssert.isNotCloseTo(n, offset(0)), intAssert.isNotCloseTo(n, withPercentage(0)));
}
@AfterTemplate

View File

@@ -15,11 +15,7 @@ final class AssertJLongTemplates {
@BeforeTemplate
AbstractLongAssert<?> before(AbstractLongAssert<?> longAssert, long n) {
return Refaster.anyOf(
longAssert.isCloseTo(n, offset(0L)),
longAssert.isCloseTo(Long.valueOf(n), offset(0L)),
longAssert.isCloseTo(n, withPercentage(0)),
longAssert.isCloseTo(Long.valueOf(n), withPercentage(0)),
longAssert.isEqualTo(Long.valueOf(n)));
longAssert.isCloseTo(n, offset(0L)), longAssert.isCloseTo(n, withPercentage(0)));
}
@AfterTemplate
@@ -32,11 +28,7 @@ final class AssertJLongTemplates {
@BeforeTemplate
AbstractLongAssert<?> before(AbstractLongAssert<?> longAssert, long n) {
return Refaster.anyOf(
longAssert.isNotCloseTo(n, offset(0L)),
longAssert.isNotCloseTo(Long.valueOf(n), offset(0L)),
longAssert.isNotCloseTo(n, withPercentage(0)),
longAssert.isNotCloseTo(Long.valueOf(n), withPercentage(0)),
longAssert.isNotEqualTo(Long.valueOf(n)));
longAssert.isNotCloseTo(n, offset(0L)), longAssert.isNotCloseTo(n, withPercentage(0)));
}
@AfterTemplate

View File

@@ -0,0 +1,35 @@
package tech.picnic.errorprone.refastertemplates;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Map;
import org.assertj.core.api.AbstractMapAssert;
final class AssertJMapTemplates {
private AssertJMapTemplates() {}
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.isEqualTo(map);
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.containsExactlyInAnyOrderEntriesOf(map);
}
}
static final class AbstractMapAssertContainsExactlyEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, K key, V value) {
return mapAssert.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(key, value));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, K key, V value) {
return mapAssert.containsExactlyEntriesOf(ImmutableMap.of(key, value));
}
}
}

View File

@@ -1,8 +1,13 @@
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.NotMatches;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.assertj.core.api.AbstractBigDecimalAssert;
@@ -14,11 +19,12 @@ import org.assertj.core.api.AbstractIntegerAssert;
import org.assertj.core.api.AbstractLongAssert;
import org.assertj.core.api.AbstractShortAssert;
import org.assertj.core.api.NumberAssert;
import tech.picnic.errorprone.refaster.matchers.IsCharacter;
final class AssertJNumberTemplates {
private AssertJNumberTemplates() {}
static final class NumberIsPositive {
static final class NumberAssertIsPositive {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -69,7 +75,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberIsNotPositive {
static final class NumberAssertIsNotPositive {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -120,7 +126,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberIsNegative {
static final class NumberAssertIsNegative {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -171,7 +177,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberIsNotNegative {
static final class NumberAssertIsNotNegative {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -221,4 +227,54 @@ final class AssertJNumberTemplates {
return numberAssert.isNotNegative();
}
}
/**
* 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(@NotMatches(IsCharacter.class) int number) {
return assertThat(number % 2).isEqualTo(1);
}
@BeforeTemplate
AbstractLongAssert<?> before(long number) {
return assertThat(number % 2).isEqualTo(1);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
NumberAssert<?, ?> after(long number) {
return assertThat(number).isOdd();
}
}
/**
* 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(@NotMatches(IsCharacter.class) int number) {
return assertThat(number % 2).isEqualTo(0);
}
@BeforeTemplate
AbstractLongAssert<?> before(long number) {
return assertThat(number % 2).isEqualTo(0);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
NumberAssert<?, ?> after(long number) {
return assertThat(number).isEven();
}
}
}

View File

@@ -1,8 +1,8 @@
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.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -21,7 +21,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object) {
return assertThat(object).isInstanceOf(Refaster.<T>clazz());
}
@@ -34,7 +34,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object) {
return assertThat(object).isNotInstanceOf(Refaster.<T>clazz());
}
@@ -47,7 +47,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object1, T object2) {
return assertThat(object1).isEqualTo(object2);
}
@@ -60,7 +60,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object1, T object2) {
return assertThat(object1).isNotEqualTo(object2);
}
@@ -73,7 +73,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectAssert<T> after(T object, String str) {
return assertThat(object).hasToString(str);
}

View File

@@ -1,8 +1,8 @@
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.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -26,7 +26,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, T> after(Optional<T> optional) {
return assertThat(optional).get();
}
@@ -53,7 +53,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
OptionalAssert<T> after(Optional<T> optional) {
return assertThat(optional).isPresent();
}
@@ -80,7 +80,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
OptionalAssert<T> after(Optional<T> optional) {
return assertThat(optional).isEmpty();
}
@@ -97,12 +97,24 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractOptionalAssert<?, T> after(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return optionalAssert.hasValue(value);
}
}
static final class AbstractOptionalAssertContainsSame<T> {
@BeforeTemplate
AbstractAssert<?, ?> before(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return Refaster.anyOf(
optionalAssert.get().isSameAs(value), optionalAssert.isPresent().isSameAs(value));
}
@AfterTemplate
AbstractOptionalAssert<?, T> after(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return optionalAssert.containsSame(value);
}
}
static final class AssertThatOptionalHasValueMatching<T> {
@BeforeTemplate
AbstractOptionalAssert<?, T> before(Optional<T> optional, Predicate<? super T> predicate) {
@@ -110,7 +122,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, T> after(Optional<T> optional, Predicate<? super T> predicate) {
return assertThat(optional).get().matches(predicate);
}

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

@@ -15,11 +15,7 @@ final class AssertJShortTemplates {
@BeforeTemplate
AbstractShortAssert<?> before(AbstractShortAssert<?> shortAssert, short n) {
return Refaster.anyOf(
shortAssert.isCloseTo(n, offset((short) 0)),
shortAssert.isCloseTo(Short.valueOf(n), offset((short) 0)),
shortAssert.isCloseTo(n, withPercentage(0)),
shortAssert.isCloseTo(Short.valueOf(n), withPercentage(0)),
shortAssert.isEqualTo(Short.valueOf(n)));
shortAssert.isCloseTo(n, offset((short) 0)), shortAssert.isCloseTo(n, withPercentage(0)));
}
@AfterTemplate
@@ -33,10 +29,7 @@ final class AssertJShortTemplates {
AbstractShortAssert<?> before(AbstractShortAssert<?> shortAssert, short n) {
return Refaster.anyOf(
shortAssert.isNotCloseTo(n, offset((short) 0)),
shortAssert.isNotCloseTo(Short.valueOf(n), offset((short) 0)),
shortAssert.isNotCloseTo(n, withPercentage(0)),
shortAssert.isNotCloseTo(Short.valueOf(n), withPercentage(0)),
shortAssert.isNotEqualTo(Short.valueOf(n)));
shortAssert.isNotCloseTo(n, withPercentage(0)));
}
@AfterTemplate

View File

@@ -1,8 +1,8 @@
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.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -31,7 +31,7 @@ final class AssertJStringTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(String string) {
assertThat(string).isEmpty();
}
@@ -56,9 +56,35 @@ final class AssertJStringTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string) {
return assertThat(string).isNotEmpty();
}
}
static final class AssertThatMatches {
@BeforeTemplate
AbstractAssert<?, ?> before(String string, String regex) {
return assertThat(string.matches(regex)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string, String regex) {
return assertThat(string).matches(regex);
}
}
static final class AssertThatDoesNotMatch {
@BeforeTemplate
AbstractAssert<?, ?> before(String string, String regex) {
return assertThat(string.matches(regex)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string, String regex) {
return assertThat(string).doesNotMatch(regex);
}
}
}

View File

@@ -1,5 +1,6 @@
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.common.collect.ImmutableBiMap;
@@ -12,10 +13,10 @@ import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multiset;
import com.google.errorprone.refaster.ImportPolicy;
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.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
@@ -38,6 +39,7 @@ import java.util.stream.Collector;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractComparableAssert;
import org.assertj.core.api.AbstractDoubleAssert;
import org.assertj.core.api.AbstractIntegerAssert;
@@ -51,6 +53,7 @@ import org.assertj.core.api.ObjectEnumerableAssert;
import org.assertj.core.api.OptionalDoubleAssert;
import org.assertj.core.api.OptionalIntAssert;
import org.assertj.core.api.OptionalLongAssert;
import tech.picnic.errorprone.refaster.matchers.IsArray;
/** Refaster templates related to AssertJ expressions and statements. */
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
@@ -60,7 +63,7 @@ import org.assertj.core.api.OptionalLongAssert;
// ^ And variants.
// XXX: Consider splitting this class into multiple classes.
// XXX: Some of these rules may not apply given the updated TestNG rewrite rules. Review.
// XXX: For the templates which "unwrap" explicitly enumerated collections, also introduce variants
// XXX: For the templates that "unwrap" explicitly enumerated collections, also introduce variants
// with explicitly enumerated sorted collections. (Requires that the type bound is Comparable.)
// XXX: Handle `.isEqualTo(explicitlyEnumeratedCollection)`. Can be considered equivalent to
// `.containsOnly(elements)`. (This does mean the auto-generated code needs to be more advanced.
@@ -94,7 +97,6 @@ import org.assertj.core.api.OptionalLongAssert;
// XXX: Right now we use and import `Offset.offset` and `Percentage.withPercentage`. Use the AssertJ
// methods instead. (Also in the TestNG migration.)
// ^ Also for `Tuple`!
// XXX: Use `assertThatIllegalArgumentException` and variants.
// XXX: `assertThatCode(x).isInstanceOf(clazz)` -> `assertThatThrownBy(x).isInstanceOf(clazz)`
// (etc.)
// XXX: Look into using Assertions#contentOf(URL url, Charset charset) instead of our own test
@@ -104,12 +106,12 @@ import org.assertj.core.api.OptionalLongAssert;
// candidates, such as `assertThat(ImmutableSet(foo, bar)).XXX`
// XXX: Write generic plugin to replace explicit array parameters with varargs (`new int[] {1, 2}`
// -> `1, 2`).
// XXX: Write plugin which drops any `.withFailMessage` which doesn't include a compile-time
// constant string? Most of these are useless.
// XXX: Write plugin which identifies `.get().propertyAccess()` and "pushes" this out. Would only
// XXX: Write plugin that drops any `.withFailMessage` that doesn't include a compile-time constant
// string? Most of these are useless.
// XXX: Write plugin that identifies `.get().propertyAccess()` and "pushes" this out. Would only
// nicely work for non-special types, though, cause after `extracting(propertyAccess)` many
// operations are not available...
// XXX: Write plugin which identifies repeated `assertThat(someProp.xxx)` calls and bundles these
// XXX: Write plugin that identifies repeated `assertThat(someProp.xxx)` calls and bundles these
// somehow.
// XXX: `abstractOptionalAssert.get().satisfies(pred)` ->
// `abstractOptionalAssert.hasValueSatisfying(pred)`.
@@ -134,7 +136,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
OptionalDoubleAssert after(OptionalDouble optional, double expected) {
return assertThat(optional).hasValue(expected);
}
@@ -153,7 +155,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
OptionalIntAssert after(OptionalInt optional, int expected) {
return assertThat(optional).hasValue(expected);
}
@@ -172,7 +174,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
OptionalLongAssert after(OptionalLong optional, long expected) {
return assertThat(optional).hasValue(expected);
}
@@ -338,8 +340,14 @@ final class AssertJTemplates {
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))),
iterAssert.containsExactlyInAnyOrder(element));
ImmutableMultiset.of(element))));
}
@BeforeTemplate
@SuppressWarnings("unchecked")
ObjectEnumerableAssert<?, S> before2(
ObjectEnumerableAssert<?, S> iterAssert, @NotMatches(IsArray.class) T element) {
return iterAssert.containsExactlyInAnyOrder(element);
}
@AfterTemplate
@@ -349,16 +357,28 @@ final class AssertJTemplates {
}
}
static final class AssertThatSetContainsExactlyOneElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(Set<S> set, T element) {
return assertThat(set).containsOnly(element);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectEnumerableAssert<?, S> after(Set<S> set, T element) {
return assertThat(set).containsExactly(element);
}
}
static final class ObjectEnumerableContainsOneDistinctElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(ObjectEnumerableAssert<?, S> iterAssert, T element) {
return Refaster.anyOf(
iterAssert.hasSameElementsAs(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))));
return iterAssert.hasSameElementsAs(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element)));
}
@AfterTemplate
@@ -371,13 +391,12 @@ final class AssertJTemplates {
static final class ObjectEnumerableIsSubsetOfOneElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(ObjectEnumerableAssert<?, S> iterAssert, T element) {
return Refaster.anyOf(
iterAssert.isSubsetOf(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))));
return iterAssert.isSubsetOf(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element)));
}
@AfterTemplate
@@ -395,8 +414,8 @@ final class AssertJTemplates {
@BeforeTemplate
void before(Iterable<E> iterable) {
Refaster.anyOf(
assertThat(iterable).hasSize(0),
assertThat(iterable.iterator().hasNext()).isFalse(),
assertThat(Iterables.size(iterable)).isEqualTo(0),
assertThat(Iterables.size(iterable)).isEqualTo(0L),
assertThat(Iterables.size(iterable)).isNotPositive());
}
@@ -405,13 +424,12 @@ final class AssertJTemplates {
void before(Collection<E> iterable) {
Refaster.anyOf(
assertThat(iterable.isEmpty()).isTrue(),
assertThat(iterable.size()).isEqualTo(0),
assertThat(iterable.size()).isEqualTo(0L),
assertThat(iterable.size()).isNotPositive());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Collection<E> iterable) {
assertThat(iterable).isEmpty();
}
@@ -435,7 +453,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
IterableAssert<E> after(Iterable<E> iterable) {
return assertThat(iterable).isNotEmpty();
}
@@ -453,7 +471,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
IterableAssert<E> after(Iterable<E> iterable, int length) {
return assertThat(iterable).hasSize(length);
}
@@ -466,7 +484,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Iterable<S> iterable, T element) {
return assertThat(iterable).containsExactly(element);
}
@@ -483,7 +501,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Iterable<S> iterable, T element) {
return assertThat(iterable).containsExactly(element);
}
@@ -500,7 +518,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(List<S> list1, List<T> list2) {
return assertThat(list1).containsExactlyElementsOf(list2);
}
@@ -512,15 +530,15 @@ final class AssertJTemplates {
static final class AssertThatSetsAreEqual<S, T extends S> {
@BeforeTemplate
IterableAssert<S> before(Set<S> set1, Set<T> set2) {
AbstractCollectionAssert<?, ?, S, ?> before(Set<S> set1, Set<T> set2) {
return Refaster.anyOf(
assertThat(set1).isEqualTo(set2),
assertThat(set1).containsExactlyInAnyOrderElementsOf(set2));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Set<S> set1, Set<T> set2) {
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractCollectionAssert<?, ?, S, ?> after(Set<S> set1, Set<T> set2) {
return assertThat(set1).hasSameElementsAs(set2);
}
}
@@ -531,13 +549,13 @@ final class AssertJTemplates {
static final class AssertThatMultisetsAreEqual<S, T extends S> {
@BeforeTemplate
IterableAssert<S> before(Multiset<S> multiset1, Multiset<T> multiset2) {
AbstractCollectionAssert<?, ?, S, ?> before(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).isEqualTo(multiset2);
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Multiset<S> multiset1, Multiset<T> multiset2) {
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractCollectionAssert<?, ?, S, ?> after(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).containsExactlyInAnyOrderElementsOf(multiset2);
}
}
@@ -602,8 +620,8 @@ final class AssertJTemplates {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@@ -614,7 +632,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
@@ -651,7 +669,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
@@ -666,7 +684,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
@@ -680,7 +698,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1).hasSameSizeAs(map2);
}
@@ -694,7 +712,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
@@ -707,7 +725,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
@@ -720,7 +738,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key, V value) {
return assertThat(map).containsEntry(key, value);
}
@@ -745,7 +763,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsAnyElementsOf(iterable);
}
@@ -766,7 +784,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsAnyOf(array);
}
@@ -775,19 +793,22 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsAnyOfVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsAnyOf(elements);
}
@@ -808,7 +829,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsAll(iterable);
}
@@ -829,7 +850,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).contains(array);
}
@@ -838,19 +859,21 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).contains(elements);
}
@@ -865,7 +888,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsExactlyElementsOf(iterable);
}
@@ -880,7 +903,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsExactly(array);
}
@@ -889,13 +912,14 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsExactlyVarargs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactly" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsExactly(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsExactly(elements);
}
@@ -911,13 +935,13 @@ final class AssertJTemplates {
}
@BeforeTemplate
IterableAssert<T> before2(
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, Iterable<U> iterable) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrderElementsOf(iterable);
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsExactlyInAnyOrderElementsOf(iterable);
}
@@ -932,13 +956,13 @@ final class AssertJTemplates {
}
@BeforeTemplate
IterableAssert<T> before2(
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, U[] array) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrder(array);
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsExactlyInAnyOrder(array);
}
@@ -947,6 +971,7 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsExactlyInAnyOrderVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -954,14 +979,16 @@ final class AssertJTemplates {
}
@BeforeTemplate
IterableAssert<T> before2(
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
.containsExactlyInAnyOrder(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@SuppressWarnings("ObjectEnumerableContainsExactlyOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsExactlyInAnyOrder(elements);
}
@@ -982,7 +1009,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsSequence(iterable);
}
@@ -991,13 +1018,15 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsSequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsSequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsSequence(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsSequence(elements);
}
@@ -1018,7 +1047,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsSubsequence(iterable);
}
@@ -1027,6 +1056,7 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsSubsequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsSubsequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -1034,7 +1064,8 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsSubsequence(elements);
}
@@ -1055,7 +1086,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).doesNotContainAnyElementsOf(iterable);
}
@@ -1076,7 +1107,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).doesNotContain(array);
}
@@ -1085,19 +1116,21 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamDoesNotContainVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).doesNotContain(elements);
}
@@ -1118,7 +1151,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).doesNotContainSequence(iterable);
}
@@ -1127,6 +1160,7 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamDoesNotContainSequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContainSequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -1134,7 +1168,8 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@SuppressWarnings("ObjectEnumerableDoesNotContainOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).doesNotContainSequence(elements);
}
@@ -1155,7 +1190,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).hasSameElementsAs(iterable);
}
@@ -1176,7 +1211,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsOnly(array);
}
@@ -1185,19 +1220,21 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsOnlyVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsOnly(elements);
}
@@ -1230,7 +1267,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] iterable) {
return assertThat(stream).isSubsetOf(iterable);
}
@@ -1239,19 +1276,21 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamIsSubsetOfVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).isSubsetOf(elements);
}
@@ -1270,7 +1309,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Stream<S> stream) {
assertThat(stream).isEmpty();
}
@@ -1289,7 +1328,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Stream<S> stream) {
assertThat(stream).isNotEmpty();
}
@@ -1302,7 +1341,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Stream<T> stream, int size) {
assertThat(stream).hasSize(size);
}
@@ -1319,7 +1358,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Predicate<T> predicate, T object) {
assertThat(predicate).accepts(object);
}
@@ -1332,7 +1371,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Predicate<T> predicate, T object) {
assertThat(predicate).rejects(object);
}
@@ -2173,7 +2212,7 @@ final class AssertJTemplates {
// }
//
// @AfterTemplate
// @UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
// @UseImportPolicy(STATIC_IMPORT_ALWAYS)
// IterableAssert<E> after(Iterable<E> iterable, E expected) {
// return assertThat(iterable).containsExactly(expected);
// }

View File

@@ -0,0 +1,471 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.io.IOException;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
/**
* Refaster templates related to AssertJ assertions over expressions that may throw a {@link
* Throwable} subtype.
*
* <p>For reasons of consistency we prefer {@link
* org.assertj.core.api.Assertions#assertThatThrownBy} over static methods for specific exception
* types. Note that only the most common assertion expressions are rewritten here; covering all
* cases would require the implementation of an Error Prone check instead.
*/
final class AssertJThrowingCallableTemplates {
private AssertJThrowingCallableTemplates() {}
static final class AssertThatThrownByIllegalArgumentException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIllegalArgumentException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IllegalArgumentException.class);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageNotContainingAny {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, @Repeated CharSequence values) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageNotContainingAny(values);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, @Repeated CharSequence values) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageNotContainingAny(values);
}
}
static final class AssertThatThrownByIllegalStateException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIllegalStateException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IllegalStateException.class);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageNotContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageNotContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageNotContaining(message);
}
}
static final class AssertThatThrownByNullPointerException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatNullPointerException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(NullPointerException.class);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatNullPointerException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatNullPointerException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatNullPointerException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIOException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIOException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IOException.class);
}
}
static final class AssertThatThrownByIOExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IOException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIOExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IOException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownBy {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType, ThrowingCallable throwingCallable) {
return assertThatExceptionOfType(exceptionType).isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType, ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(exceptionType);
}
}
static final class AssertThatThrownByHasMessage {
@BeforeTemplate
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message) {
return assertThatExceptionOfType(exceptionType)
.isThrownBy(throwingCallable)
.withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message) {
return assertThatThrownBy(throwingCallable).isInstanceOf(exceptionType).hasMessage(message);
}
}
static final class AssertThatThrownByHasMessageParameters {
@BeforeTemplate
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatExceptionOfType(exceptionType)
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(exceptionType)
.hasMessage(message, parameters);
}
}
// XXX: Drop this template in favour of a generic Error Prone check that flags
// `String.format(...)` arguments to a wide range of format methods.
static final class AbstractThrowableAssertHasMessage {
@BeforeTemplate
AbstractThrowableAssert<?, ? extends Throwable> before(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object parameters) {
return abstractThrowableAssert.hasMessage(String.format(message, parameters));
}
@AfterTemplate
AbstractThrowableAssert<?, ? extends Throwable> after(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object parameters) {
return abstractThrowableAssert.hasMessage(message, parameters);
}
}
// XXX: Drop this template in favour of a generic Error Prone check that flags
// `String.format(...)` arguments to a wide range of format methods.
static final class AbstractThrowableAssertWithFailMessage {
@BeforeTemplate
AbstractThrowableAssert<?, ? extends Throwable> before(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object args) {
return abstractThrowableAssert.withFailMessage(String.format(message, args));
}
@AfterTemplate
AbstractThrowableAssert<?, ? extends Throwable> after(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object args) {
return abstractThrowableAssert.withFailMessage(message, args);
}
}
}

View File

@@ -1,16 +1,18 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.toImmutableEnumSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.disjoint;
import static java.util.Objects.checkIndex;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -38,12 +40,12 @@ final class AssortedTemplates {
static final class CheckIndex {
@BeforeTemplate
int before(int index, int size) {
return Preconditions.checkElementIndex(index, size);
return checkElementIndex(index, size);
}
@AfterTemplate
int after(int index, int size) {
return Objects.checkIndex(index, size);
return checkIndex(index, size);
}
}
@@ -62,14 +64,14 @@ final class AssortedTemplates {
}
static final class MapGetOrNull<K, V, L> {
@Nullable
@BeforeTemplate
@Nullable
V before(Map<K, V> map, L key) {
return map.getOrDefault(key, null);
}
@Nullable
@AfterTemplate
@Nullable
V after(Map<K, V> map, L key) {
return map.get(key);
}
@@ -80,7 +82,7 @@ final class AssortedTemplates {
* ImmutableSet#toImmutableSet()} and produces a more compact object.
*
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving: while the
* original code produces a set which iterates over the elements in encounter order, the
* original code produces a set that iterates over the elements in encounter order, the
* replacement code iterates over the elements in enum definition order.
*/
// XXX: ^ Consider emitting a comment warning about this fact?
@@ -91,7 +93,7 @@ final class AssortedTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableEnumSet());
}
@@ -107,8 +109,8 @@ final class AssortedTemplates {
Streams.stream(iterator).findAny().orElse(defaultValue));
}
@Nullable
@AfterTemplate
@Nullable
T after(Iterator<T> iterator, T defaultValue) {
return Iterators.getNext(iterator, defaultValue);
}
@@ -116,7 +118,7 @@ final class AssortedTemplates {
/** Don't unnecessarily repeat boolean expressions. */
// XXX: This template captures only the simplest case. `@AlsoNegation` doesn't help. Consider
// contributing a Refaster patch which handles the negation in the `@BeforeTemplate` more
// contributing a Refaster patch, which handles the negation in the `@BeforeTemplate` more
// intelligently.
static final class LogicalImplication {
@BeforeTemplate
@@ -161,7 +163,7 @@ final class AssortedTemplates {
@AfterTemplate
boolean after(Set<T> set1, Set<T> set2) {
return Collections.disjoint(set1, set2);
return disjoint(set1, set2);
}
}
@@ -176,15 +178,15 @@ final class AssortedTemplates {
@BeforeTemplate
boolean before(Collection<T> collection1, Collection<T> collection2) {
return Refaster.anyOf(
Collections.disjoint(ImmutableSet.copyOf(collection1), collection2),
Collections.disjoint(new HashSet<>(collection1), collection2),
Collections.disjoint(collection1, ImmutableSet.copyOf(collection2)),
Collections.disjoint(collection1, new HashSet<>(collection2)));
disjoint(ImmutableSet.copyOf(collection1), collection2),
disjoint(new HashSet<>(collection1), collection2),
disjoint(collection1, ImmutableSet.copyOf(collection2)),
disjoint(collection1, new HashSet<>(collection2)));
}
@AfterTemplate
boolean after(Collection<T> collection1, Collection<T> collection2) {
return Collections.disjoint(collection1, collection2);
return disjoint(collection1, collection2);
}
}
@@ -256,11 +258,6 @@ final class AssortedTemplates {
//
// @BeforeTemplate
// void before(Supplier<T> supplier) {
// anyStatement(supplier::get);
// }
//
// @BeforeTemplate
// void before2(Supplier<T> supplier) {
// anyStatement(() -> supplier.get());
// }
//

View File

@@ -10,11 +10,14 @@ import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.IntFunction;
import java.util.stream.Stream;
/** Refaster templates related to expressions dealing with (arbitrary) collections. */
@@ -37,6 +40,11 @@ final class CollectionTemplates {
Iterables.isEmpty(collection));
}
@BeforeTemplate
boolean before(ImmutableCollection<T> collection) {
return collection.asList().isEmpty();
}
@AfterTemplate
@AlsoNegation
boolean after(Collection<T> collection) {
@@ -51,6 +59,11 @@ final class CollectionTemplates {
return Iterables.size(collection);
}
@BeforeTemplate
int before(ImmutableCollection<T> collection) {
return collection.asList().size();
}
@AfterTemplate
int after(Collection<T> collection) {
return collection.size();
@@ -117,31 +130,32 @@ final class CollectionTemplates {
}
}
static final class CollectionRemoveAllFromCollectionBlock<T, S extends T> {
static final class SetRemoveAllCollection<T, S extends T> {
@BeforeTemplate
void before(Collection<T> removeTo, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeTo::remove);
void before(Set<T> removeFrom, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeFrom::remove);
}
@BeforeTemplate
void before2(Collection<T> removeTo, Collection<S> elementsToRemove) {
void before2(Set<T> removeFrom, Collection<S> elementsToRemove) {
for (T element : elementsToRemove) {
removeTo.remove(element);
removeFrom.remove(element);
}
}
// XXX: This method is identical to `before2` except for the loop type. Make Refaster smarter so
// that this is supported out of the box.
// that this is supported out of the box. After doing so, also drop the `S extends T` type
// constraint; ideally this check applies to any `S`.
@BeforeTemplate
void before3(Collection<T> removeTo, Collection<S> elementsToRemove) {
void before3(Set<T> removeFrom, Collection<S> elementsToRemove) {
for (S element : elementsToRemove) {
removeTo.remove(element);
removeFrom.remove(element);
}
}
@AfterTemplate
void after(Collection<T> removeTo, Collection<S> elementsToRemove) {
removeTo.removeAll(elementsToRemove);
void after(Set<T> removeFrom, Collection<S> elementsToRemove) {
removeFrom.removeAll(elementsToRemove);
}
}
@@ -175,17 +189,7 @@ final class CollectionTemplates {
* Don't call {@link ImmutableCollection#asList()} if the result is going to be streamed; stream
* directly.
*/
// XXX: Similar rules could be implemented for the following variants:
// collection.asList().contains(null);
// collection.asList().isEmpty();
// collection.asList().iterator();
// collection.asList().parallelStream();
// collection.asList().size();
// collection.asList().toArray();
// collection.asList().toArray(Object[]::new);
// collection.asList().toArray(new Object[0]);
// collection.asList().toString();
static final class ImmutableCollectionAsListToStream<T> {
static final class ImmutableCollectionStream<T> {
@BeforeTemplate
Stream<T> before(ImmutableCollection<T> collection) {
return collection.asList().stream();
@@ -197,6 +201,121 @@ final class CollectionTemplates {
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link Collection#contains(Object)} is
* called on the result; call it directly.
*/
static final class ImmutableCollectionContains<T, S> {
@BeforeTemplate
boolean before(ImmutableCollection<T> collection, S elem) {
return collection.asList().contains(elem);
}
@AfterTemplate
boolean after(ImmutableCollection<T> collection, S elem) {
return collection.contains(elem);
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link ImmutableCollection#parallelStream()}
* is called on the result; call it directly.
*/
static final class ImmutableCollectionParallelStream<T> {
@BeforeTemplate
Stream<T> before(ImmutableCollection<T> collection) {
return collection.asList().parallelStream();
}
@AfterTemplate
Stream<T> after(ImmutableCollection<T> collection) {
return collection.parallelStream();
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link ImmutableCollection#toString()} is
* called on the result; call it directly.
*/
static final class ImmutableCollectionToString<T> {
@BeforeTemplate
String before(ImmutableCollection<T> collection) {
return collection.asList().toString();
}
@AfterTemplate
String after(ImmutableCollection<T> collection) {
return collection.toString();
}
}
/** Prefer calling {@link Collection#toArray()} over more contrived alternatives. */
static final class CollectionToArray<T> {
@BeforeTemplate
Object[] before(Collection<T> collection, int size) {
return Refaster.anyOf(
collection.toArray(new Object[size]), collection.toArray(Object[]::new));
}
@BeforeTemplate
Object[] before(ImmutableCollection<T> collection) {
return collection.asList().toArray();
}
@AfterTemplate
Object[] after(Collection<T> collection) {
return collection.toArray();
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link
* ImmutableCollection#toArray(Object[])}` is called on the result; call it directly.
*/
static final class ImmutableCollectionToArrayWithArray<T, S> {
@BeforeTemplate
Object[] before(ImmutableCollection<T> collection, S[] array) {
return collection.asList().toArray(array);
}
@AfterTemplate
Object[] after(ImmutableCollection<T> collection, S[] array) {
return collection.toArray(array);
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link
* ImmutableCollection#toArray(IntFunction)}} is called on the result; call it directly.
*/
static final class ImmutableCollectionToArrayWithGenerator<T, S> {
@BeforeTemplate
S[] before(ImmutableCollection<T> collection, IntFunction<S[]> generator) {
return collection.asList().toArray(generator);
}
@AfterTemplate
S[] after(ImmutableCollection<T> collection, IntFunction<S[]> generator) {
return collection.toArray(generator);
}
}
/**
* Don't call {@link ImmutableCollection#asList()} if {@link ImmutableCollection#iterator()} is
* called on the result; call it directly.
*/
static final class ImmutableCollectionIterator<T> {
@BeforeTemplate
Iterator<T> before(ImmutableCollection<T> collection) {
return collection.asList().iterator();
}
@AfterTemplate
Iterator<T> after(ImmutableCollection<T> collection) {
return collection.iterator();
}
}
/**
* Don't use the ternary operator to extract the first element of a possibly-empty {@link
* Collection} as an {@link Optional}.

View File

@@ -1,12 +1,17 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingDouble;
import static java.util.Comparator.comparingInt;
import static java.util.Comparator.comparingLong;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -14,6 +19,7 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
@@ -29,14 +35,13 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
Comparator.comparing(Refaster.anyOf(identity(), v -> v)),
Comparator.<T>reverseOrder().reversed());
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return Comparator.naturalOrder();
return naturalOrder();
}
}
@@ -48,7 +53,7 @@ final class ComparatorTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return reverseOrder();
}
@@ -58,11 +63,11 @@ final class ComparatorTemplates {
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp) {
return Comparator.comparing(Refaster.anyOf(identity(), v -> v), cmp);
return comparing(Refaster.anyOf(identity(), v -> v), cmp);
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after(Comparator<T> cmp) {
return cmp;
}
@@ -72,7 +77,7 @@ final class ComparatorTemplates {
static final class ThenComparing<S, T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<S> before(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(Comparator.comparing(function));
return cmp.thenComparing(comparing(function));
}
@AfterTemplate
@@ -85,11 +90,11 @@ final class ComparatorTemplates {
static final class ThenComparingReversed<S, T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<S> before(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(Comparator.comparing(function).reversed());
return cmp.thenComparing(comparing(function).reversed());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<S> after(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(function, reverseOrder());
}
@@ -100,7 +105,7 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<S> before(
Comparator<S> cmp, Function<? super S, ? extends T> function, Comparator<? super T> cmp2) {
return cmp.thenComparing(Comparator.comparing(function, cmp2));
return cmp.thenComparing(comparing(function, cmp2));
}
@AfterTemplate
@@ -115,7 +120,7 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<S> before(
Comparator<S> cmp, Function<? super S, ? extends T> function, Comparator<? super T> cmp2) {
return cmp.thenComparing(Comparator.comparing(function, cmp2).reversed());
return cmp.thenComparing(comparing(function, cmp2).reversed());
}
@AfterTemplate
@@ -129,7 +134,7 @@ final class ComparatorTemplates {
static final class ThenComparingDouble<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToDoubleFunction<? super T> function) {
return cmp.thenComparing(Comparator.comparingDouble(function));
return cmp.thenComparing(comparingDouble(function));
}
@AfterTemplate
@@ -142,7 +147,7 @@ final class ComparatorTemplates {
static final class ThenComparingInt<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToIntFunction<? super T> function) {
return cmp.thenComparing(Comparator.comparingInt(function));
return cmp.thenComparing(comparingInt(function));
}
@AfterTemplate
@@ -155,7 +160,7 @@ final class ComparatorTemplates {
static final class ThenComparingLong<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToLongFunction<? super T> function) {
return cmp.thenComparing(Comparator.comparingLong(function));
return cmp.thenComparing(comparingLong(function));
}
@AfterTemplate
@@ -176,9 +181,9 @@ final class ComparatorTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after(Comparator<T> cmp) {
return cmp.thenComparing(Comparator.naturalOrder());
return cmp.thenComparing(naturalOrder());
}
}
@@ -255,4 +260,36 @@ final class ComparatorTemplates {
return Comparators.max(value1, value2, cmp);
}
}
/**
* Prefer a method reference to {@link Comparators#min(Comparable, Comparable)} over calling
* {@link BinaryOperator#minBy(Comparator)} with {@link Comparator#naturalOrder()}.
*/
static final class ComparatorsMin<T extends Comparable<? super T>> {
@BeforeTemplate
BinaryOperator<T> before() {
return BinaryOperator.minBy(naturalOrder());
}
@AfterTemplate
BinaryOperator<T> after() {
return Comparators::min;
}
}
/**
* Prefer a method reference to {@link Comparators#max(Comparable, Comparable)} over calling
* {@link BinaryOperator#minBy(Comparator)} with {@link Comparator#naturalOrder()}.
*/
static final class ComparatorsMax<T extends Comparable<? super T>> {
@BeforeTemplate
BinaryOperator<T> before() {
return BinaryOperator.maxBy(naturalOrder());
}
@AfterTemplate
BinaryOperator<T> after() {
return Comparators::max;
}
}
}

View File

@@ -233,10 +233,7 @@ final class DoubleStreamTemplates {
static final class DoubleStreamAllMatch {
@BeforeTemplate
boolean before(DoubleStream stream, DoublePredicate predicate) {
return Refaster.anyOf(
stream.noneMatch(predicate.negate()),
!stream.anyMatch(predicate.negate()),
stream.filter(predicate.negate()).findAny().isEmpty());
return stream.noneMatch(predicate.negate());
}
@AfterTemplate
@@ -251,10 +248,7 @@ final class DoubleStreamTemplates {
@BeforeTemplate
boolean before(DoubleStream stream) {
return Refaster.anyOf(
stream.noneMatch(e -> !test(e)),
!stream.anyMatch(e -> !test(e)),
stream.filter(e -> !test(e)).findAny().isEmpty());
return stream.noneMatch(e -> !test(e));
}
@AfterTemplate

View File

@@ -4,7 +4,6 @@ import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.NoAutoboxing;
import java.util.Objects;
import java.util.function.Predicate;
@@ -12,61 +11,41 @@ import java.util.function.Predicate;
final class EqualityTemplates {
private EqualityTemplates() {}
/** Prefer primitive/reference-based quality for primitives and enums. */
static final class PrimitiveOrReferenceEquality {
@NoAutoboxing
@BeforeTemplate
boolean before(boolean a, boolean b) {
return Objects.equals(a, b);
}
@NoAutoboxing
@BeforeTemplate
boolean before(long a, long b) {
return Objects.equals(a, b);
}
@NoAutoboxing
@BeforeTemplate
boolean before(double a, double b) {
return Objects.equals(a, b);
}
/** Prefer reference-based quality for enums. */
// Primitive value comparisons are not listed, because Error Prone flags those out of the box.
static final class PrimitiveOrReferenceEquality<T extends Enum<T>> {
/**
* Enums can be compared by reference. It is safe to do so even in the face of refactorings,
* because if the type is ever converted to a non-enum, then Error-Prone will complain about any
* remaining reference-based equality checks.
*/
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
// work around the issue by selecting the "largest replacements". See RefasterCheck.
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
@BeforeTemplate
<T extends Enum<T>> boolean before(T a, T b) {
boolean before(T a, T b) {
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
}
@AlsoNegation
@AfterTemplate
boolean after(boolean a, boolean b) {
@AlsoNegation
boolean after(T a, T b) {
return a == b;
}
}
/** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsageCheck` tries to
// achieve. If/when `MethodReferenceUsageCheck` becomes production ready, we should simply drop
// this check.
// XXX: Alternatively, the rule should be replaced with a plugin which also identifies cases where
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
// If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.
// XXX: Alternatively, the rule should be replaced with a plugin that also identifies cases where
// the arguments are swapped but simplification is possible anyway, by virtue of `v` being
// non-null.
static final class EqualsPredicate<T> {
@BeforeTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> before(T v) {
return e -> v.equals(e);
}
@AfterTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> after(T v) {
return v::equals;
}
@@ -89,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);
@@ -120,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

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.function.Function.identity;
import com.google.common.collect.ImmutableListMultimap;
@@ -12,7 +13,6 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -33,8 +33,8 @@ final class ImmutableListMultimapTemplates {
* Prefer {@link ImmutableListMultimap#builder()} over the associated constructor on constructions
* that produce a less-specific type.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableListMultimapBuilder<K, V> {
@BeforeTemplate
ImmutableMultimap.Builder<K, V> before() {
@@ -56,10 +56,7 @@ final class ImmutableListMultimapTemplates {
static final class EmptyImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableMultimap<K, V> before() {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().build(),
ImmutableMultimap.<K, V>builder().build(),
ImmutableMultimap.of());
return Refaster.anyOf(ImmutableListMultimap.<K, V>builder().build(), ImmutableMultimap.of());
}
@AfterTemplate
@@ -80,7 +77,6 @@ final class ImmutableListMultimapTemplates {
ImmutableMultimap<K, V> before(K key, V value) {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().put(key, value).build(),
ImmutableMultimap.<K, V>builder().put(key, value).build(),
ImmutableMultimap.of(key, value));
}
@@ -96,12 +92,11 @@ final class ImmutableListMultimapTemplates {
*/
static final class EntryToImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
ImmutableListMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().put(entry).build(),
Stream.of(entry).collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)),
ImmutableMultimap.<K, V>builder().put(entry).build(),
ImmutableMultimap.of(entry.getKey(), entry.getValue()));
Stream.of(entry)
.collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)));
}
@AfterTemplate
@@ -118,8 +113,7 @@ final class ImmutableListMultimapTemplates {
ImmutableListMultimap.copyOf(iterable.entries()),
ImmutableListMultimap.<K, V>builder().putAll(iterable).build(),
ImmutableMultimap.copyOf(iterable),
ImmutableMultimap.copyOf(iterable.entries()),
ImmutableMultimap.<K, V>builder().putAll(iterable).build());
ImmutableMultimap.copyOf(iterable.entries()));
}
@BeforeTemplate
@@ -129,7 +123,6 @@ final class ImmutableListMultimapTemplates {
ImmutableListMultimap.<K, V>builder().putAll(iterable).build(),
Streams.stream(iterable)
.collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)),
ImmutableMultimap.<K, V>builder().putAll(iterable).build(),
ImmutableMultimap.copyOf(iterable));
}
@@ -148,8 +141,8 @@ final class ImmutableListMultimapTemplates {
}
/**
* Don't map a a stream's elements to map entries, only to subsequently collect them into an
* {@link ImmutableListMultimap}. The collection can be performed directly.
* Don't map stream's elements to map entries, only to subsequently collect them into an {@link
* ImmutableListMultimap}. The collection can be performed directly.
*/
abstract static class StreamOfMapEntriesToImmutableListMultimap<E, K, V> {
@Placeholder(allowsIdentity = true)
@@ -168,7 +161,7 @@ final class ImmutableListMultimapTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableListMultimap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableListMultimap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -279,17 +272,4 @@ final class ImmutableListMultimapTemplates {
return ImmutableListMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableListMultimap}. */
static final class ImmutableListMultimapCopyOfImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableListMultimap<K, V> before(ImmutableListMultimap<K, V> multimap) {
return ImmutableListMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableListMultimap<K, V> after(ImmutableListMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -2,21 +2,20 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
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 java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -27,8 +26,8 @@ final class ImmutableListTemplates {
private ImmutableListTemplates() {}
/** Prefer {@link ImmutableList#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableListBuilder<T> {
@BeforeTemplate
ImmutableList.Builder<T> before() {
@@ -41,48 +40,15 @@ final class ImmutableListTemplates {
}
}
/** Prefer {@link ImmutableList#of()} over more contrived alternatives. */
static final class EmptyImmutableList<T> {
@BeforeTemplate
ImmutableList<T> before() {
return Refaster.anyOf(
ImmutableList.<T>builder().build(), Stream.<T>empty().collect(toImmutableList()));
}
@AfterTemplate
ImmutableList<T> after() {
return ImmutableList.of();
}
}
/**
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
// XXX: Note that this rewrite rule is incorrect for nullable elements.
static final class SingletonImmutableList<T> {
@BeforeTemplate
List<T> before(T element) {
return Collections.singletonList(element);
}
@AfterTemplate
ImmutableList<T> after(T element) {
return ImmutableList.of(element);
}
}
/**
* Prefer {@link ImmutableList#copyOf(Iterable)} and variants over more contrived alternatives.
*/
static final class IterableToImmutableList<T> {
// XXX: Drop the inner `Refaster.anyOf` if/when we introduce a rule to choose between one and
// the other.
@BeforeTemplate
ImmutableList<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableList.<T>builder().add(iterable).build(),
Refaster.anyOf(Stream.of(iterable), Arrays.stream(iterable)).collect(toImmutableList()));
Arrays.stream(iterable).collect(toImmutableList()));
}
@BeforeTemplate
@@ -110,40 +76,20 @@ final class ImmutableListTemplates {
}
}
/** Prefer {@link ImmutableList#toImmutableList()} over the more verbose alternative. */
// XXX: Once the code base has been sufficiently cleaned up, we might want to also rewrite
// `Collectors.toList(`), with the caveat that it allows mutation (though this cannot be relied
// upon) as well as nulls. Another option is to explicitly rewrite those variants to
// `Collectors.toSet(ArrayList::new)`.
/** Prefer {@link ImmutableList#toImmutableList()} over less idiomatic alternatives. */
static final class StreamToImmutableList<T> {
@BeforeTemplate
ImmutableList<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableList.copyOf(stream.iterator()),
ImmutableList.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableList::copyOf)));
return ImmutableList.copyOf(stream.iterator());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableList<T> after(Stream<T> stream) {
return stream.collect(toImmutableList());
}
}
/** Don't call {@link ImmutableList#asList()}; it is a no-op. */
static final class ImmutableListAsList<T> {
@BeforeTemplate
ImmutableList<T> before(ImmutableList<T> list) {
return list.asList();
}
@AfterTemplate
ImmutableList<T> after(ImmutableList<T> list) {
return list;
}
}
/** Prefer {@link ImmutableList#sortedCopyOf(Iterable)} over more contrived alternatives. */
static final class ImmutableListSortedCopyOf<T extends Comparable<? super T>> {
@BeforeTemplate
@@ -197,9 +143,122 @@ final class ImmutableListTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableList<T> after(Stream<T> stream) {
return stream.collect(toImmutableSet()).asList();
}
}
/**
* Prefer {@link ImmutableList#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
// this and similar Refaster templates are replaced with an Error Prone check.
static final class ImmutableListOf<T> {
@BeforeTemplate
List<T> before() {
return Refaster.anyOf(
ImmutableList.<T>builder().build(),
Stream.<T>empty().collect(toImmutableList()),
emptyList(),
List.of());
}
@AfterTemplate
ImmutableList<T> after() {
return ImmutableList.of();
}
}
/**
* Prefer {@link ImmutableList#of(Object)} over more contrived alternatives or alternatives that
* don't communicate the immutability of the resulting list at the type level.
*/
// XXX: Note that the replacement of `Collections#singletonList` is incorrect for nullable
// elements.
static final class ImmutableListOf1<T> {
@BeforeTemplate
List<T> before(T e1) {
return Refaster.anyOf(
ImmutableList.<T>builder().add(e1).build(), singletonList(e1), List.of(e1));
}
@AfterTemplate
ImmutableList<T> after(T e1) {
return ImmutableList.of(e1);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf2<T> {
@BeforeTemplate
List<T> before(T e1, T e2) {
return List.of(e1, e2);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2) {
return ImmutableList.of(e1, e2);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf3<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3) {
return List.of(e1, e2, e3);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3) {
return ImmutableList.of(e1, e2, e3);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf4<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3, T e4) {
return List.of(e1, e2, e3, e4);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3, T e4) {
return ImmutableList.of(e1, e2, e3, e4);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object, Object, Object)} over alternatives that
* don't communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf5<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3, T e4, T e5) {
return List.of(e1, e2, e3, e4, e5);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3, T e4, T e5) {
return ImmutableList.of(e1, e2, e3, e4, e5);
}
}
}

View File

@@ -1,12 +1,14 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.function.Function.identity;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -14,7 +16,6 @@ import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -26,8 +27,8 @@ final class ImmutableMapTemplates {
private ImmutableMapTemplates() {}
/** Prefer {@link ImmutableMap#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableMapBuilder<K, V> {
@BeforeTemplate
ImmutableMap.Builder<K, V> before() {
@@ -40,41 +41,6 @@ final class ImmutableMapTemplates {
}
}
/** Prefer {@link ImmutableMap#of()} over more contrived alternatives. */
static final class EmptyImmutableMap<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before() {
return ImmutableMap.<K, V>builder().build();
}
@AfterTemplate
ImmutableMap<K, V> after() {
return ImmutableMap.of();
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives and
* alternatives that don't communicate the immutability of the resulting map at the type level..
*/
// XXX: One can define variants for more than one key-value pair, but at some point the builder
// actually produces nicer code. So it's not clear we should add Refaster templates for those
// variants.
// XXX: Note that the `singletonMap` rewrite rule is incorrect for nullable elements.
static final class PairToImmutableMap<K, V> {
@BeforeTemplate
Map<K, V> before(K key, V value) {
return Refaster.anyOf(
ImmutableMap.<K, V>builder().put(key, value).build(),
Collections.singletonMap(key, value));
}
@AfterTemplate
ImmutableMap<K, V> after(K key, V value) {
return ImmutableMap.of(key, value);
}
}
/** Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives. */
static final class EntryToImmutableMap<K, V> {
@BeforeTemplate
@@ -169,7 +135,7 @@ final class ImmutableMapTemplates {
abstract V valueFunction(@MayOptionallyUse E element);
// XXX: We could add variants in which the entry is created some other way, but we have another
// rule which covers canonicalization to `Map.entry`.
// rule that covers canonicalization to `Map.entry`.
@BeforeTemplate
ImmutableMap<K, V> before(Stream<E> stream) {
return stream
@@ -178,7 +144,7 @@ final class ImmutableMapTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableMap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableMap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -226,7 +192,7 @@ final class ImmutableMapTemplates {
// XXX: Instead of `Map.Entry::getKey` we could also match `e -> e.getKey()`. But for some
// reason Refaster doesn't handle that case. This doesn't matter if we roll out use of
// `MethodReferenceUsageCheck`. Same observation applies to a lot of other Refaster checks.
// `MethodReferenceUsage`. Same observation applies to a lot of other Refaster checks.
@BeforeTemplate
@SuppressWarnings("NullAway")
ImmutableMap<K, V2> before(Map<K, V1> map) {
@@ -242,16 +208,108 @@ final class ImmutableMapTemplates {
}
}
/** Don't unnecessarily copy an {@link ImmutableMap}. */
static final class ImmutableMapCopyOfImmutableMap<K, V> {
/**
* Prefer {@link ImmutableMap#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting map at the type level.
*/
static final class ImmutableMapOf<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(map);
Map<K, V> before() {
return Refaster.anyOf(ImmutableMap.<K, V>builder().build(), emptyMap(), Map.of());
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return map;
ImmutableMap<K, V> after() {
return ImmutableMap.of();
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives or alternatives
* that don't communicate the immutability of the resulting map at the type level.
*/
// XXX: Note that the replacement of `Collections#singletonMap` is incorrect for nullable
// elements.
static final class ImmutableMapOf1<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1) {
return Refaster.anyOf(
ImmutableMap.<K, V>builder().put(k1, v1).build(), singletonMap(k1, v1), Map.of(k1, v1));
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1) {
return ImmutableMap.of(k1, v1);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting map at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf2<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2) {
return Map.of(k1, v1, k2, v2);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2) {
return ImmutableMap.of(k1, v1, k2, v2);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object)} over
* alternatives that don't communicate the immutability of the resulting map at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf3<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3) {
return Map.of(k1, v1, k2, v2, k3, v3);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object, Object, Object)}
* over alternatives that don't communicate the immutability of the resulting map at the type
* level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf4<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
return Map.of(k1, v1, k2, v2, k3, v3, k4, v4);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object, Object, Object,
* Object, Object)} over alternatives that don't communicate the immutability of the resulting map
* at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf5<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
}
}

View File

@@ -1,12 +1,10 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -21,8 +19,8 @@ final class ImmutableMultisetTemplates {
private ImmutableMultisetTemplates() {}
/** Prefer {@link ImmutableMultiset#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableMultisetBuilder<T> {
@BeforeTemplate
ImmutableMultiset.Builder<T> before() {
@@ -54,14 +52,11 @@ final class ImmutableMultisetTemplates {
* alternatives.
*/
static final class IterableToImmutableMultiset<T> {
// XXX: Drop the inner `Refaster.anyOf` if/when we introduce a rule to choose between one and
// the other.
@BeforeTemplate
ImmutableMultiset<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableMultiset.<T>builder().add(iterable).build(),
Refaster.anyOf(Stream.of(iterable), Arrays.stream(iterable))
.collect(toImmutableMultiset()));
Arrays.stream(iterable).collect(toImmutableMultiset()));
}
@BeforeTemplate
@@ -93,29 +88,13 @@ final class ImmutableMultisetTemplates {
static final class StreamToImmutableMultiset<T> {
@BeforeTemplate
ImmutableMultiset<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableMultiset.copyOf(stream.iterator()),
ImmutableMultiset.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableMultiset::copyOf)));
return ImmutableMultiset.copyOf(stream.iterator());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableMultiset<T> after(Stream<T> stream) {
return stream.collect(toImmutableMultiset());
}
}
/** Don't unnecessarily copy an {@link ImmutableMultiset}. */
static final class ImmutableMultisetCopyOfImmutableMultiset<T> {
@BeforeTemplate
ImmutableMultiset<T> before(ImmutableMultiset<T> multiset) {
return ImmutableMultiset.copyOf(multiset);
}
@AfterTemplate
ImmutableMultiset<T> after(ImmutableMultiset<T> multiset) {
return multiset;
}
}
}

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSetMultimap.flatteningToImmutableSetMultimap;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ListMultimap;
@@ -10,7 +11,6 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -27,8 +27,8 @@ final class ImmutableSetMultimapTemplates {
private ImmutableSetMultimapTemplates() {}
/** Prefer {@link ImmutableSetMultimap#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableSetMultimapBuilder<K, V> {
@BeforeTemplate
ImmutableSetMultimap.Builder<K, V> before() {
@@ -138,7 +138,7 @@ final class ImmutableSetMultimapTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableSetMultimap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableSetMultimap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -162,7 +162,7 @@ final class ImmutableSetMultimapTemplates {
@AfterTemplate
ImmutableSetMultimap<K, V2> after(Multimap<K, V1> multimap) {
return ImmutableSetMultimap.copyOf(
Multimaps.transformValues(multimap, v -> valueTransformation(v)));
Multimaps.transformValues(multimap, e -> valueTransformation(e)));
}
}
@@ -215,17 +215,4 @@ final class ImmutableSetMultimapTemplates {
return ImmutableSetMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableSetMultimap}. */
static final class ImmutableSetMultimapCopyOfImmutableSetMultimap<K, V> {
@BeforeTemplate
ImmutableSetMultimap<K, V> before(ImmutableSetMultimap<K, V> multimap) {
return ImmutableSetMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableSetMultimap<K, V> after(ImmutableSetMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -1,21 +1,19 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets.SetView;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
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 java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Stream;
@@ -25,8 +23,8 @@ final class ImmutableSetTemplates {
private ImmutableSetTemplates() {}
/** Prefer {@link ImmutableSet#builder()} over the associated constructor. */
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableSetBuilder<T> {
@BeforeTemplate
ImmutableSet.Builder<T> before() {
@@ -39,46 +37,13 @@ final class ImmutableSetTemplates {
}
}
/** Prefer {@link ImmutableSet#of()} over more contrived alternatives. */
static final class EmptyImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before() {
return Refaster.anyOf(
ImmutableSet.<T>builder().build(), Stream.<T>empty().collect(toImmutableSet()));
}
@AfterTemplate
ImmutableSet<T> after() {
return ImmutableSet.of();
}
}
/**
* Prefer {@link ImmutableSet#of(Object)} over alternatives that don't communicate the
* immutability of the resulting set at the type level.
*/
// XXX: Note that this rewrite rule is incorrect for nullable elements.
static final class SingletonImmutableSet<T> {
@BeforeTemplate
Set<T> before(T element) {
return Collections.singleton(element);
}
@AfterTemplate
ImmutableSet<T> after(T element) {
return ImmutableSet.of(element);
}
}
/** Prefer {@link ImmutableSet#copyOf(Iterable)} and variants over more contrived alternatives. */
static final class IterableToImmutableSet<T> {
// XXX: Drop the inner `Refaster.anyOf` if/when we introduce a rule to choose between one and
// the other.
@BeforeTemplate
ImmutableSet<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSet.<T>builder().add(iterable).build(),
Refaster.anyOf(Stream.of(iterable), Arrays.stream(iterable)).collect(toImmutableSet()));
Arrays.stream(iterable).collect(toImmutableSet()));
}
@BeforeTemplate
@@ -107,41 +72,20 @@ final class ImmutableSetTemplates {
}
/** Prefer {@link ImmutableSet#toImmutableSet()} over less idiomatic alternatives. */
// XXX: Once the code base has been sufficiently cleaned up, we might want to also rewrite
// `Collectors.toSet(`), with the caveat that it allows mutation (though this cannot be relied
// upon) as well as nulls. Another option is to explicitly rewrite those variants to
// `Collectors.toSet(HashSet::new)`.
static final class StreamToImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableSet.copyOf(stream.iterator()),
ImmutableSet.copyOf(stream::iterator),
stream.distinct().collect(toImmutableSet()),
stream.collect(collectingAndThen(toList(), ImmutableSet::copyOf)),
stream.collect(collectingAndThen(toSet(), ImmutableSet::copyOf)));
ImmutableSet.copyOf(stream.iterator()), stream.distinct().collect(toImmutableSet()));
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableSet());
}
}
/** Don't unnecessarily copy an {@link ImmutableSet}. */
static final class ImmutableSetCopyOfImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before(ImmutableSet<T> set) {
return ImmutableSet.copyOf(set);
}
@AfterTemplate
ImmutableSet<T> after(ImmutableSet<T> set) {
return set;
}
}
/** Prefer {@link SetView#immutableCopy()} over the more verbose alternative. */
static final class ImmutableSetCopyOfSetView<T> {
@BeforeTemplate
@@ -154,4 +98,115 @@ final class ImmutableSetTemplates {
return set.immutableCopy();
}
}
/**
* Prefer {@link ImmutableSet#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting set at the type level.
*/
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
// this and similar Refaster templates are replaced with an Error Prone check.
static final class ImmutableSetOf<T> {
@BeforeTemplate
Set<T> before() {
return Refaster.anyOf(
ImmutableSet.<T>builder().build(),
Stream.<T>empty().collect(toImmutableSet()),
emptySet(),
Set.of());
}
@AfterTemplate
ImmutableSet<T> after() {
return ImmutableSet.of();
}
}
/**
* Prefer {@link ImmutableSet#of(Object)} over more contrived alternatives or alternatives that
* don't communicate the immutability of the resulting set at the type level.
*/
// XXX: Note that the replacement of `Collections#singleton` is incorrect for nullable elements.
static final class ImmutableSetOf1<T> {
@BeforeTemplate
Set<T> before(T e1) {
return Refaster.anyOf(ImmutableSet.<T>builder().add(e1).build(), singleton(e1), Set.of(e1));
}
@AfterTemplate
ImmutableSet<T> after(T e1) {
return ImmutableSet.of(e1);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object)} over alternatives that don't communicate the
* immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf2<T> {
@BeforeTemplate
Set<T> before(T e1, T e2) {
return Set.of(e1, e2);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2) {
return ImmutableSet.of(e1, e2);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object)} over alternatives that don't communicate
* the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf3<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3) {
return Set.of(e1, e2, e3);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3) {
return ImmutableSet.of(e1, e2, e3);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf4<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3, T e4) {
return Set.of(e1, e2, e3, e4);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3, T e4) {
return ImmutableSet.of(e1, e2, e3, e4);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object, Object)} over alternatives that
* don't communicate the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check that also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf5<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3, T e4, T e5) {
return Set.of(e1, e2, e3, e4, e5);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3, T e4, T e5) {
return ImmutableSet.of(e1, e2, e3, e4, e5);
}
}
}

View File

@@ -35,8 +35,8 @@ final class ImmutableSortedMapTemplates {
* Prefer {@link ImmutableSortedMap#naturalOrder()} over the alternative that requires explicitly
* providing the {@link Comparator}.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableSortedMapNaturalOrderBuilder<K extends Comparable<? super K>, V> {
@BeforeTemplate
ImmutableSortedMap.Builder<K, V> before() {
@@ -53,8 +53,8 @@ final class ImmutableSortedMapTemplates {
* Prefer {@link ImmutableSortedMap#reverseOrder()} over the alternative that requires explicitly
* providing the {@link Comparator}.
*/
// XXX: This drops generic type information, sometimes leading to non-compilable code. Anything
// we can do about that?
// XXX: This drops generic type information, sometimes leading to non-compilable code. See
// https://github.com/google/error-prone/pull/2706.
static final class ImmutableSortedMapReverseOrderBuilder<K extends Comparable<? super K>, V> {
@BeforeTemplate
ImmutableSortedMap.Builder<K, V> before() {

View File

@@ -1,14 +1,11 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSortedMultiset.toImmutableSortedMultiset;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -92,18 +89,15 @@ final class ImmutableSortedMultisetTemplates {
// XXX: There's also a variant with a custom Comparator. (And some special cases with
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedMultiset<T extends Comparable<? super T>> {
// XXX: Drop the inner `Refaster.anyOf` if/when we introduce a rule to choose between one and
// the other.
@BeforeTemplate
ImmutableMultiset<T> before(T[] iterable) {
ImmutableSortedMultiset<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.<T>naturalOrder().add(iterable).build(),
Refaster.anyOf(Stream.of(iterable), Arrays.stream(iterable))
.collect(toImmutableSortedMultiset(naturalOrder())));
Arrays.stream(iterable).collect(toImmutableSortedMultiset(naturalOrder())));
}
@BeforeTemplate
ImmutableMultiset<T> before(Iterator<T> iterable) {
ImmutableSortedMultiset<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),
@@ -111,7 +105,7 @@ final class ImmutableSortedMultisetTemplates {
}
@BeforeTemplate
ImmutableMultiset<T> before(Iterable<T> iterable) {
ImmutableSortedMultiset<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),
@@ -137,14 +131,11 @@ final class ImmutableSortedMultisetTemplates {
static final class StreamToImmutableSortedMultiset<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedMultiset<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(stream.iterator()),
ImmutableSortedMultiset.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableSortedMultiset::copyOf)));
return ImmutableSortedMultiset.copyOf(stream.iterator());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableSortedMultiset<T> after(Stream<T> stream) {
return stream.collect(toImmutableSortedMultiset(naturalOrder()));
}

View File

@@ -1,14 +1,11 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -90,18 +87,15 @@ final class ImmutableSortedSetTemplates {
// XXX: There's also a variant with a custom Comparator. (And some special cases with
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedSet<T extends Comparable<? super T>> {
// XXX: Drop the inner `Refaster.anyOf` if/when we introduce a rule to choose between one and
// the other.
@BeforeTemplate
ImmutableSet<T> before(T[] iterable) {
ImmutableSortedSet<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedSet.<T>naturalOrder().add(iterable).build(),
Refaster.anyOf(Stream.of(iterable), Arrays.stream(iterable))
.collect(toImmutableSortedSet(naturalOrder())));
Arrays.stream(iterable).collect(toImmutableSortedSet(naturalOrder())));
}
@BeforeTemplate
ImmutableSet<T> before(Iterator<T> iterable) {
ImmutableSortedSet<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),
@@ -109,7 +103,7 @@ final class ImmutableSortedSetTemplates {
}
@BeforeTemplate
ImmutableSet<T> before(Iterable<T> iterable) {
ImmutableSortedSet<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),
@@ -137,14 +131,11 @@ final class ImmutableSortedSetTemplates {
static final class StreamToImmutableSortedSet<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedSet<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(stream.iterator()),
ImmutableSortedSet.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableSortedSet::copyOf)));
return ImmutableSortedSet.copyOf(stream.iterator());
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ImmutableSortedSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableSortedSet(naturalOrder()));
}

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