Compare commits

...

79 Commits

Author SHA1 Message Date
Rick Ossendrijver
cf38b37c8c Have ReverseOrder rule flag Collections.reverseOrder(String::compareTo) 2022-12-08 09:07:10 +01:00
Stephan Schroevers
b1c815770b Prevent ReverseOrder Refaster rule from introducing a static import (#397)
This is a workaround for the issue resolved by google/error-prone#3584.

After application of this Refaster rule, any static imports of
`java.util.Collections.reverseOrder` are obsolete. These can be removed by
running Google Java Format or Error Prone's `RemoveUnusedImports` check.

Where possible, subsequent application of the `StaticImport` check will
statically import `java.util.Comparator.reverseOrder`.
2022-12-08 09:06:19 +01:00
Vincent Koeman
bc1f204877 Prefer BigDecimal.valueOf(double) over new BigDecimal(double) (#394)
See https://rules.sonarsource.com/java/RSPEC-2111
2022-12-07 18:58:54 +01:00
Picnic-Bot
cf995ece2b Upgrade actions/setup-java v3.6.0 -> v3.8.0 (#395)
See:
- https://github.com/actions/setup-java/releases/tag/v3.8.0
- https://github.com/actions/setup-java/compare/v3.6.0...v3.8.0
2022-12-07 11:48:55 +01:00
Stephan Schroevers
d427e298e2 Introduce additional Refaster rules to ComparatorRules (#388) 2022-12-07 11:38:25 +01:00
Picnic-Bot
ae327d8d64 Upgrade pitest-maven-plugin 1.9.11 -> 1.10.3 (#378)
See:
- https://github.com/hcoles/pitest/releases/tag/1.10.0
- https://github.com/hcoles/pitest/releases/tag/1.10.1
- https://github.com/hcoles/pitest/releases/tag/1.10.2
- https://github.com/hcoles/pitest/releases/tag/1.10.3
- https://github.com/hcoles/pitest/compare/1.9.11...1.10.3
2022-12-06 11:58:47 +01:00
Gonzalo Amestoy
a6f794de3d Introduce CollectionForEach Refaster rule (#390)
Fixes #387.
2022-12-06 09:28:24 +01:00
Picnic-Bot
1794d36053 Upgrade Pitest Git plugins 1.0.2 -> 1.0.3 (#391) 2022-12-06 09:04:37 +01:00
Phil Werli
ee62af4a86 Introduce MonoFromOptionalSwitchIfEmpty and OptionalMapMonoJust Refaster rules (#384) 2022-12-06 08:26:34 +01:00
Phil Werli
1afce12b52 Introduce Mono{Empty,Just,JustOrEmpty} Refaster rules (#385) 2022-12-06 08:15:39 +01:00
Rick Ossendrijver
f585306a1f Downgrade actions/setup-java v3.7.0 -> v3.6.0 (#392)
This reverts commit 5afa7e1878. Tag v3.7.0 was
deleted; see actions/setup-java#422 for details.
2022-12-06 08:06:58 +01:00
Luke Prananta
4f9aba83ec Introduce StringCaseLocaleUsage check (#376) 2022-12-05 13:49:20 +01:00
Stephan Schroevers
066591c379 Improve mutation testing setup (#383)
Summary of changes:
- Enable Pitest's built-in `STRONGER` mutator group.
- Enable Arcmutate's `EXTENDED` mutator group.
- Enable Arcmutate's JUnit 5 Accelerator Plugin.
- Modify `MoreTypesTest` such that it is impacted by mutations of the
  `MoreTypes` class.

See:
- https://pitest.org/quickstart/mutators/
- https://docs.arcmutate.com/docs/extended-operators.html
- https://docs.arcmutate.com/docs/accelerator.html
2022-12-05 09:11:51 +01:00
Picnic-Bot
789f8c86f2 Upgrade Pitest Git plugins 1.0.1 -> 1.0.2 (#380) 2022-12-05 09:01:20 +01:00
Picnic-Bot
0ccebcc9c4 Upgrade Checker Framework Annotations 3.27.0 -> 3.28.0 (#382)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.28.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.28.0
2022-12-04 19:57:42 +01:00
Gijs de Jong
8803d23a8e Introduce JUnitClassModifiers check (#214) 2022-12-04 16:49:01 +01:00
Picnic-Bot
5afa7e1878 Upgrade actions/setup-java v3.6.0 -> v3.7.0 (#381)
See:
- https://github.com/actions/setup-java/releases/tag/v3.7.0
- https://github.com/actions/setup-java/compare/v3.6.0...v3.7.0
2022-12-02 09:49:13 +01:00
Picnic-Bot
8e3beb9d5c Upgrade ruby/setup-ruby v1.123.0 -> v1.126.0 (#379)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.124.0
- https://github.com/ruby/setup-ruby/releases/tag/v1.125.0
- https://github.com/ruby/setup-ruby/releases/tag/v1.126.0
- https://github.com/ruby/setup-ruby/compare/v1.123.0...v1.126.0
2022-12-01 09:01:08 +01:00
Picnic-Bot
96ab66cdcf Upgrade maven-dependency-plugin 3.3.0 -> 3.4.0 (#377)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEP%20AND%20fixVersion%20%3E%203.3.0%20AND%20fixVersion%20%3C%3D%203.4.0%20AND%20statusCategory%20%3D%20Done%20
- https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.3.0...maven-dependency-plugin-3.4.0
2022-11-30 16:44:09 +01:00
Stephan Schroevers
330328329f Report mutation test coverage of proposed changes (#346)
Thanks to a free Arcmutate OSS license, GitHub Actions now runs Pitest against
files changed in the context of a PR. 

While there, update Pitest's configuration to ignore Refaster rule collection
classes, as mutations of those classes will not impact the associated unit
tests.

See:
- https://www.arcmutate.com
- https://pitest.org
2022-11-30 09:14:58 +01:00
Phil Werli
f46859ae3a Introduce some Refaster rules that avoid nested Publishers (#374) 2022-11-28 16:24:55 +01:00
Christos Giallouros
6d15cfe7ff Introduce {Mono,Flux}DefaultIfEmpty Refaster rules (#370)
Resolves #363.
2022-11-28 13:15:25 +01:00
Picnic-Bot
fa1bb8aa94 Upgrade Spring Boot 2.7.5 -> 2.7.6 (#372)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.6
- https://github.com/spring-projects/spring-boot/compare/v2.7.5...v2.7.6
2022-11-28 13:01:20 +01:00
Picnic-Bot
415ae35906 Upgrade SLF4J API 2.0.4 -> 2.0.5 (#371)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_2.0.4...v_2.0.5
2022-11-28 12:46:35 +01:00
Picnic-Bot
7cc8abc3de Upgrade Checkstyle 10.4 -> 10.5.0 (#375)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.5.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.4...checkstyle-10.5.0
2022-11-28 09:04:31 +01:00
Picnic-Bot
16c8bb0b27 Upgrade actions/configure-pages v2.1.2 -> v2.1.3 (#316)
See:
- https://github.com/actions/configure-pages/releases/tag/v2.1.3
- https://github.com/actions/configure-pages/compare/v2.1.2...v2.1.3
2022-11-24 08:37:02 +01:00
Rick Ossendrijver
609d80c9fa Drop unused Palantir {assertj,baseline}-error-prone dependencies (#367) 2022-11-23 16:20:04 +01:00
Picnic-Bot
793bda50d2 Upgrade Jackson 2.14.0 -> 2.14.1 (#366)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.14.1
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.14.0...jackson-bom-2.14.1
2022-11-23 08:14:27 +01:00
Picnic-Bot
5d86a66791 Upgrade actions/deploy-pages v1.2.2 -> v1.2.3 (#365)
See:
- https://github.com/actions/deploy-pages/releases/tag/v1.2.3
- https://github.com/actions/deploy-pages/compare/v1.2.2...v1.2.3
2022-11-22 14:27:40 +01:00
Eric Staffas
84d425e4ca Introduce More{ASTHelpers,JUnitMatchers,Matchers} utility classes (#335) 2022-11-22 13:35:03 +01:00
Picnic-Bot
4fead24e73 Upgrade maven-install-plugin 3.0.1 -> 3.1.0 (#362)
See:
- https://github.com/apache/maven-install-plugin/releases/tag/maven-install-plugin-3.1.0
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.0.1...maven-install-plugin-3.1.0
2022-11-21 22:23:25 +01:00
Picnic-Bot
92df829801 Upgrade SLF4J API 2.0.3 -> 2.0.4 (#360)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_2.0.3...v_2.0.4
2022-11-21 15:44:14 +01:00
Stephan Schroevers
98185b92ae Improve Tree deletion suggestions (#347)
When suggesting to remove a method or method annotation, also remove any
trailing whitespace. This avoids the possible introduction of an empty
line right at the start of a code block.
2022-11-21 13:01:32 +01:00
Picnic-Bot
1b6356a876 Upgrade NullAway 0.10.4 -> 0.10.5 (#359)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.4...v0.10.5
2022-11-19 13:31:29 +01:00
Picnic-Bot
919a7c7ebe Upgrade AutoValue 1.10 -> 1.10.1 (#361)
See:
- https://github.com/google/auto/releases/tag/auto-value-1.10.1
- https://github.com/google/auto/compare/auto-value-1.10...auto-value-1.10.1
2022-11-19 13:16:30 +01:00
Phil Werli
79b0123f41 Extend MonoFlux Refaster rule (#358) 2022-11-19 11:56:44 +01:00
Picnic-Bot
3967542edf Upgrade Spring 5.3.23 -> 5.3.24 (#355)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.24
- https://github.com/spring-projects/spring-framework/compare/v5.3.23...v5.3.24
2022-11-19 10:30:34 +01:00
Rick Ossendrijver
0f05d15dd2 Have Renovate file ruby/setup-ruby upgrade PRs at most once a month (#357) 2022-11-18 14:16:20 +01:00
Picnic-Bot
5bdb90634a Upgrade New Relic Java Agent 7.11.0 -> 7.11.1 (#350)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.11.1
- https://github.com/newrelic/newrelic-java-agent/compare/v7.11.0...v7.11.1
2022-11-18 10:44:45 +01:00
Picnic-Bot
1808164c0d Upgrade swagger-annotations 1.6.8 -> 1.6.9 (#352)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.9
- https://github.com/swagger-api/swagger-core/compare/v1.6.8...v1.6.9
2022-11-18 10:21:53 +01:00
Picnic-Bot
f451b4cb73 Upgrade ruby/setup-ruby v1.121.0 -> v1.123.0 (#345)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.122.0
- https://github.com/ruby/setup-ruby/releases/tag/v1.123.0
- https://github.com/ruby/setup-ruby/compare/v1.121.0...v1.123.0
2022-11-18 09:39:42 +01:00
Picnic-Bot
d4c95299eb Upgrade pitest-maven-plugin 1.9.10 -> 1.9.11 (#354)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.11
- https://github.com/hcoles/pitest/compare/1.9.10...1.9.11
2022-11-18 09:16:08 +01:00
Picnic-Bot
1b93843d62 Upgrade swagger-annotations 2.2.6 -> 2.2.7 (#351)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.7
- https://github.com/swagger-api/swagger-core/compare/v2.2.6...v2.2.7
2022-11-17 16:37:39 +01:00
Picnic-Bot
3929b39e62 Upgrade actions/upload-pages-artifact v1.0.4 -> v1.0.5 (#353)
See:
- https://github.com/actions/upload-pages-artifact/releases/tag/v1.0.5
- https://github.com/actions/upload-pages-artifact/compare/v1.0.4...v1.0.5
2022-11-17 14:22:02 +01:00
Picnic-Bot
81f701fd9e Upgrade Mockito 4.8.1 -> 4.9.0 (#349)
See:
- https://github.com/mockito/mockito/releases/tag/v4.9.0
- https://github.com/mockito/mockito/compare/v4.8.1...v4.9.0
2022-11-15 09:19:39 +01:00
Picnic-Bot
b040648400 Upgrade modernizer-maven-plugin 2.4.0 -> 2.5.0 (#348)
See:
- https://github.com/gaul/modernizer-maven-plugin/releases/tag/modernizer-maven-plugin-2.5.0
- https://github.com/gaul/modernizer-maven-plugin/compare/modernizer-maven-plugin-2.4.0...modernizer-maven-plugin-2.5.0
2022-11-14 09:41:07 +01:00
Picnic-Bot
79ca98d20f Upgrade pitest-maven-plugin 1.9.9 -> 1.9.10 (#344)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.10
- https://github.com/hcoles/pitest/compare/1.9.9...1.9.10
2022-11-12 16:08:30 +01:00
Picnic-Bot
a6122270a9 Upgrade Project Reactor 2020.0.24 -> 2022.0.0 (#342)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.0
- https://github.com/reactor/reactor/compare/2020.0.24...2022.0.0
2022-11-12 15:50:15 +01:00
Picnic-Bot
4c0f2c966c Upgrade ruby/setup-ruby v1.120.1 -> v1.121.0 (#343)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.121.0
- https://github.com/ruby/setup-ruby/compare/v1.120.1...v1.121.0
2022-11-12 13:38:33 +01:00
Picnic-Bot
778a6e7043 Upgrade ruby/setup-ruby v1.120.0 -> v1.120.1 (#341)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.120.1
- https://github.com/ruby/setup-ruby/compare/v1.120.0...v1.120.1
2022-11-10 07:45:10 +01:00
Luke Prananta
b6146efaf6 Introduce MapIsEmpty Refaster rule (#339) 2022-11-09 10:45:53 +01:00
Stephan Schroevers
fcaa1f7068 Improve and extend Refaster Map rules (#337)
Summary of changes:
- Move relevant rules from `AssertJRules` to `AssertJMapRules` and introduce 
  associated tests.
- Extract relevant rules from `AssortedRules` to the new `MapRules` Refaster 
  rule collection.
- Add a few new rules to both `AssertJMapRules` and `MapRules`.
2022-11-09 08:54:43 +01:00
Picnic-Bot
ece33b0061 Upgrade Jackson 2.13.4.20221013 -> 2.14.0 (#338)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.14
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.4.20221013...jackson-bom-2.14.0
2022-11-07 10:34:44 +01:00
Phil Werli
48772a044e Introduce Optional{Filter,Map} Refaster rules (#327) 2022-11-04 19:41:23 +01:00
Shang Xiang
281534aeca Introduce Refaster rules to streamline java.time type creation (#322) 2022-11-04 19:27:20 +01:00
Bastien Diederichs
42e632e5db Introduce IsInstanceLambdaUsage check (#323) 2022-11-04 11:47:29 +01:00
Hervé Boutemy
7febccb7ff Add Reproducible Builds badge to README (#333) 2022-11-03 20:27:45 +01:00
Guillaume Toison
d5c1c858d5 Configure documentation URL for StringJoin check (#331) 2022-11-03 16:15:59 +01:00
Picnic-Bot
55d2622380 Upgrade Checker Framework Annotations 3.26.0 -> 3.27.0 (#330)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.27.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.26.0...checker-framework-3.27.0
2022-11-03 08:56:18 +01:00
Picnic-Bot
f10f2c9209 Upgrade NullAway 0.10.3 -> 0.10.4 (#328)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.3...v0.10.4
2022-11-03 08:18:25 +01:00
Picnic-Bot
99f85614fd Upgrade swagger-annotations 2.2.4 -> 2.2.6 (#329)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.5
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.6
- https://github.com/swagger-api/swagger-core/compare/v2.2.4...v2.2.6
2022-11-03 08:00:58 +01:00
Stephan Schroevers
be24edadae [maven-release-plugin] prepare for next development iteration 2022-11-01 08:56:43 +01:00
Stephan Schroevers
068c03708b [maven-release-plugin] prepare release v0.5.0 2022-11-01 08:56:40 +01:00
Picnic-Bot
9c330981ea Upgrade Checkstyle 10.3.4 -> 10.4 (#325)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.4
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.4...checkstyle-10.4
2022-10-31 08:15:38 +01:00
Bastien Diederichs
b780c05dc0 Introduce assorted Reactor error handling Refaster rules (#318) 2022-10-30 16:45:40 +01:00
Picnic-Bot
16955a9cfa Upgrade NullAway 0.10.2 -> 0.10.3 (#324)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.2...v0.10.3
2022-10-30 16:34:31 +01:00
Stephan Schroevers
8fa3ff3702 By default, prevent BugCheckers from introducing new dependencies (#308) 2022-10-29 13:42:51 +02:00
Picnic-Bot
022a3d293e Upgrade New Relic Java Agent 7.10.0 -> 7.11.0 (#320)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.11.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.10.0...v7.11.0
2022-10-28 08:56:32 +02:00
Picnic-Bot
81227cdd94 Upgrade tidy-maven-plugin 1.1.0 -> 1.2.0 (#271)
See:
- https://github.com/mojohaus/tidy-maven-plugin/releases/tag/tidy-maven-plugin-1.2.0
- https://github.com/mojohaus/tidy-maven-plugin/compare/tidy-maven-plugin-1.1.0...tidy-maven-plugin-1.2.0
2022-10-27 16:28:43 +02:00
Stephan Schroevers
04d886c031 Improve build and deployment concurrency handling (#284)
Builds for the same branch are now serialized. Among other things this prevents
concurrent deployments.

While there, reduce the permission assigned to each of the two jobs.
2022-10-27 10:31:06 +02:00
Cernat Catalin Stefan
afb2a28dcf Introduce {Mono,Flux}Map{,NotNull} Refaster rules (#142) 2022-10-26 17:11:16 +02:00
Phil Werli
dc0f90e981 Introduce {Mono,Flux}#zipWith{,Iterable} Refaster rules (#293) 2022-10-26 10:53:27 +02:00
Elena Liashenko
2196bbd8f9 Have FluxFlatMapUsage better handle nested Publishers (#224) 2022-10-26 10:40:09 +02:00
Picnic-Bot
f3b81304b9 Upgrade pitest-maven-plugin 1.9.8 -> 1.9.9 (#136)
See https://github.com/hcoles/pitest/compare/1.9.8...1.9.9
2022-10-26 10:00:34 +02:00
Picnic-Bot
b0d374040a Upgrade ruby/setup-ruby v1.118.0 -> v1.120.0 (#317)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.119.0
- https://github.com/ruby/setup-ruby/releases/tag/v1.120.0
- https://github.com/ruby/setup-ruby/compare/v1.118.0...v1.120.0
2022-10-26 09:19:33 +02:00
Eric Staffas
45dfc53d40 Prefer Flux#take(long, boolean) over Flux#take(long) to limit upstream generation (#314) 2022-10-26 08:28:35 +02:00
Rick Ossendrijver
6cb10ffe2f Build and test on additional platforms and against additional JDKs (#301) 2022-10-25 17:51:24 +02:00
Stephan Schroevers
92f2b0ab0f Introduce MoreTypes utility class (#234)
The static methods of this class allow one to construct complex types,
against which expression types can subsequently be matched.
2022-10-25 17:13:43 +02:00
chamil-prabodha
21388273c5 Have TimeZoneUsage check flag {OffsetDate,Offset,ZonedDate}Time#now (#311) 2022-10-25 16:54:28 +02:00
79 changed files with 4486 additions and 568 deletions

View File

@@ -7,10 +7,27 @@ permissions:
contents: read
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
jdk: [ 11.0.16, 17.0.4 ]
os: [ ubuntu-22.04 ]
jdk: [ 11.0.16, 17.0.4, 19 ]
distribution: [ temurin ]
experimental: [ false ]
include:
- os: macos-12
jdk: 17.0.4
distribution: temurin
experimental: false
- os: windows-2022
jdk: 17.0.4
distribution: temurin
experimental: false
- os: ubuntu-22.04
jdk: 20-ea
distribution: zulu
experimental: true
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
steps:
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
@@ -20,10 +37,10 @@ jobs:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.6.0
uses: actions/setup-java@v3.8.0
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
distribution: ${{ matrix.distribution }}
cache: maven
- name: Display build environment details
run: mvn --version

View File

@@ -3,25 +3,22 @@ on:
pull_request:
push:
branches: [ master, website ]
permissions:
contents: read
id-token: write
pages: write
concurrency:
group: pages
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
permissions:
contents: read
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
- uses: ruby/setup-ruby@v1.118.0
- uses: ruby/setup-ruby@v1.126.0
with:
working-directory: ./website
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@v2.1.2
uses: actions/configure-pages@v2.1.3
- name: Generate documentation
run: ./generate-docs.sh
- name: Build website with Jekyll
@@ -33,12 +30,15 @@ jobs:
# "Refaster rules" terminology on our website and in the code.
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.4
uses: actions/upload-pages-artifact@v1.0.5
with:
path: ./website/_site
deploy:
if: github.ref == 'refs/heads/website'
needs: build
permissions:
id-token: write
pages: write
runs-on: ubuntu-22.04
environment:
name: github-pages
@@ -46,4 +46,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1.2.2
uses: actions/deploy-pages@v1.2.3

37
.github/workflows/pitest-analyze-pr.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# Performs mutation testing analysis on the files changed by a pull request and
# uploads the results. The associated PR is subsequently updated by the
# `pitest-update-pr.yml` workflow. See https://blog.pitest.org/oss-pitest-pr/
# for details.
name: "Mutation testing"
on:
pull_request:
permissions:
contents: read
jobs:
analyze-pr:
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Run Pitest
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
# analyzes lines changed in the associated pull request, as GitHub
# exposes the changes unique to the PR as a single commit on top of the
# target branch. See https://blog.pitest.org/pitest-pr-setup for
# details.
run: mvn test pitest:mutationCoverage -DargLine.xmx=2048m -Dverification.skip -Dfeatures="+GIT(from[HEAD~1]), +gitci"
- name: Aggregate Pitest reports
run: mvn pitest-git:aggregate -DkilledEmoji=":tada:" -DmutantEmoji=":zombie:" -DtrailingText="Mutation testing report by [Pitest](https://pitest.org/). Review any surviving mutants by inspecting the line comments under [_Files changed_](${{ github.event.number }}/files)."
- name: Upload Pitest reports as artifact
uses: actions/upload-artifact@v3.1.1
with:
name: pitest-reports
path: ./target/pit-reports-ci

35
.github/workflows/pitest-update-pr.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
# Updates a pull request based on the corresponding mutation testing analysis
# performed by the `pitest-analyze-pr.yml` workflow. See
# https://blog.pitest.org/oss-pitest-pr/ for details.
name: "Mutation testing: post results"
on:
workflow_run:
workflows: ["Mutation testing"]
types:
- completed
permissions:
actions: read
checks: write
contents: read
pull-requests: write
jobs:
update-pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Download Pitest analysis artifact
uses: dawidd6/action-download-artifact@v2.24.2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: pitest-reports
path: ./target/pit-reports-ci
- name: Update PR
run: mvn -DrepoToken="${{ secrets.GITHUB_TOKEN }}" pitest-github:updatePR

View File

@@ -10,7 +10,7 @@
},
{
"matchPackagePatterns": [
"^com\\.palantir\\.baseline:baseline-error-prone$"
"^ruby\\/setup-ruby$"
],
"schedule": "* * 1 * *"
}

View File

@@ -20,6 +20,7 @@ loves Error Prone: producing high-quality and consistent Java
code_][picnic-blog-ep-post].
[![Maven Central][maven-central-badge]][maven-central-search]
[![Reproducible Builds][reproducible-builds-badge]][reproducible-builds-report]
[![GitHub Actions][github-actions-build-badge]][github-actions-build-master]
[![License][license-badge]][license]
[![PRs Welcome][pr-badge]][contributing]
@@ -165,7 +166,7 @@ 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
- `./run-mutation-tests.sh` runs mutation tests using [Pitest][pitest]. The
results can be reviewed by opening the respective
`target/pit-reports/index.html` files. For more information check the [PIT
Maven plugin][pitest-maven].
@@ -226,3 +227,5 @@ guidelines][contributing].
[refaster]: https://errorprone.info/docs/refaster
[refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md

7
cdg-pitest-licence.txt Normal file
View File

@@ -0,0 +1,7 @@
# Arcmutate license for Error Prone Support, requested by sending an email to
# support@arcmutate.com.
expires=07/11/2023
keyVersion=1
signature=MhZxMbnO6UovNfllM0JuVWkZyvRT3/G5o/uT0Mm36c7200VpZNVu03gTAGivnl9W5RzvZhfpIHccuQ5ctjQkrqhsFSrl4fyqPqu3y5V2fsHIdFXP/G72EGj6Kay9ndLpaEHalqE0bEwxdnHMzEYq5y3O9vUPv8MhUl57xk+rvBo\=
packages=tech.picnic.errorprone.*
type=OSSS

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -93,6 +93,11 @@
<artifactId>reactor-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-extra</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
@@ -168,6 +173,11 @@
<artifactId>slf4j-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>

View File

@@ -15,7 +15,6 @@ import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
@@ -24,6 +23,7 @@ import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
@AutoService(BugChecker.class)
@@ -62,6 +62,6 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
* leave flagging the unused import to Error Prone's `RemoveUnusedImports` check.
*/
AnnotationTree annotation = Iterables.getOnlyElement(annotations);
return describeMatch(annotation, SuggestedFix.delete(annotation));
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
}
}

View File

@@ -18,6 +18,7 @@ import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.stream.Collector;
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
/**
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
@@ -50,7 +51,8 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!COLLECTOR_METHOD.matches(tree, state)) {
if (!ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)
|| !COLLECTOR_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}

View File

@@ -14,7 +14,6 @@ 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.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
@@ -22,6 +21,7 @@ import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@@ -55,7 +55,7 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
return Description.NO_MATCH;
}
return describeMatch(tree, SuggestedFix.delete(tree));
return describeMatch(tree, SourceCode.deleteWithTrailingWhitespace(tree, state));
}
private static boolean isInPossibleTestHelperClass(VisitorState state) {

View File

@@ -5,6 +5,10 @@ import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
@@ -17,11 +21,14 @@ import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
/**
@@ -33,11 +40,12 @@ import reactor.core.publisher.Flux;
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
* avoids potentially saturating the thread pool on which subscription happens. If {@code
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
* {@code flatMapSequential} overload with an explicit concurrency level.
* concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code
* flatMap} or {@code flatMapSequential} overload with an explicit concurrency level.
*
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
* by this check because there is no clear alternative to point to.
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function,
* java.util.function.Supplier)} is not flagged by this check because there is no clear alternative
* to point to.
*/
@AutoService(BugChecker.class)
@BugPattern(
@@ -52,11 +60,16 @@ public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
private static final Supplier<Type> FLUX =
Suppliers.typeFromString("reactor.core.publisher.Flux");
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.onDescendantOf(FLUX)
.namedAnyOf("flatMap", "flatMapSequential")
.withParameters(Function.class.getName());
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
VisitorState.memoize(
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
/** Instantiates a new {@link FluxFlatMapUsage} instance. */
public FluxFlatMapUsage() {}
@@ -67,14 +80,27 @@ public final class FluxFlatMapUsage extends BugChecker
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
.addFix(
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build())
.build();
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
SuggestedFix concurrencyCapFix =
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build();
Description.Builder description = buildDescription(tree);
if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) {
/*
* Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g.
* if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit
* concurrently bound, in favour of sequential subscriptions using `Flux#concatMap`.
*/
description.addFix(concurrencyCapFix).addFix(serializationFix);
} else {
description.addFix(serializationFix).addFix(concurrencyCapFix);
}
return description.build();
}
@Override

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.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.Tree.Kind;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
* of the form {@code T.class::isInstance}.
*
* @see MethodReferenceUsage
*/
// XXX: Consider folding this logic into the `MethodReferenceUsage` check.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
link = BUG_PATTERNS_BASE_URL + "IsInstanceLambdaUsage",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link IsInstanceLambdaUsage} instance. */
public IsInstanceLambdaUsage() {}
@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
if (tree.getKind() != Kind.LAMBDA_EXPRESSION || tree.getBody().getKind() != Kind.INSTANCE_OF) {
return Description.NO_MATCH;
}
return describeMatch(
tree,
SuggestedFix.replace(
tree,
SourceCode.treeToString(((InstanceOfTree) tree.getBody()).getType(), state)
+ ".class::isInstance"));
}
}

View File

@@ -0,0 +1,82 @@
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.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.hasMethod;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
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.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.ClassTree;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} that flags non-final and non package-private JUnit test class declarations,
* unless abstract.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Non-abstract JUnit test classes should be declared package-private and final",
linkType = CUSTOM,
link = BUG_PATTERNS_BASE_URL + "JUnitClassModifiers",
severity = SUGGESTION,
tags = STYLE)
public final class JUnitClassModifiers extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ClassTree> HAS_SPRING_CONFIGURATION_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.springframework.context.annotation.Configuration"),
hasMetaAnnotation("org.springframework.context.annotation.Configuration")));
private static final Matcher<ClassTree> TEST_CLASS_WITH_INCORRECT_MODIFIERS =
allOf(
hasMethod(TEST_METHOD),
not(hasModifier(Modifier.ABSTRACT)),
anyOf(
hasModifier(Modifier.PRIVATE),
hasModifier(Modifier.PROTECTED),
hasModifier(Modifier.PUBLIC),
allOf(not(hasModifier(Modifier.FINAL)), not(HAS_SPRING_CONFIGURATION_ANNOTATION))));
/** Instantiates a new {@link JUnitClassModifiers} instance. */
public JUnitClassModifiers() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
if (!TEST_CLASS_WITH_INCORRECT_MODIFIERS.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(
tree.getModifiers(),
state,
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC))
.ifPresent(fixBuilder::merge);
if (!HAS_SPRING_CONFIGURATION_ANNOTATION.matches(tree, state)) {
SuggestedFixes.addModifiers(tree, state, Modifier.FINAL).ifPresent(fixBuilder::merge);
}
return describeMatch(tree, fixBuilder.build());
}
}

View File

@@ -13,6 +13,8 @@ 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 static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
@@ -25,18 +27,13 @@ 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.MoreASTHelpers;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
@@ -64,20 +61,6 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
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")));
/** Instantiates a new {@link JUnitMethodDeclaration} instance. */
public JUnitMethodDeclaration() {}
@@ -142,7 +125,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
if (isMethodInEnclosingClass(methodName, state)) {
if (MoreASTHelpers.methodExistsInEnclosingClass(methodName, state)) {
return Optional.of(
String.format("a method named `%s` already exists in this class", methodName));
}
@@ -158,15 +141,6 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
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)
@@ -188,18 +162,4 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
.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

@@ -37,6 +37,8 @@ import javax.lang.model.element.Name;
/**
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
*
* @see IsInstanceLambdaUsage
*/
// XXX: Other custom expressions we could rewrite:
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
@@ -50,6 +52,9 @@ import javax.lang.model.element.Name;
// `not(some::reference)`.
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
// XXX: Expressions of the form `i -> SomeType.class.isInstance(i)` are not replaced; fix that using
// a suitable generalization.
// XXX: Consider folding the `IsInstanceLambdaUsage` check into this class.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer method references over lambda expressions",

View File

@@ -4,9 +4,11 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -16,9 +18,7 @@ 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}. */
@@ -33,24 +33,16 @@ import java.util.Optional;
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
/** Instantiates a new {@link NestedOptionals} instance. */
public NestedOptionals() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return 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);
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
? describeMatch(tree)
: Description.NO_MATCH;
}
}

View File

@@ -29,7 +29,7 @@ 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
* A {@link BugChecker} that suggests a canonical set of modifiers for Refaster class and method
* definitions.
*/
@AutoService(BugChecker.class)

View File

@@ -26,6 +26,7 @@ import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
/**
* A {@link BugChecker} that flags methods with Spring's {@code @Scheduled} annotation that lack New
@@ -51,7 +52,8 @@ public final class ScheduledTransactionTrace extends BugChecker implements Metho
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_SCHEDULED.matches(tree, state)) {
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|| !IS_SCHEDULED.matches(tree, state)) {
return Description.NO_MATCH;
}

View File

@@ -101,7 +101,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
"org.springframework.http.HttpMethod",
"org.springframework.http.MediaType",
"org.testng.Assert",
"reactor.function.TupleUtils");
"reactor.function.TupleUtils",
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
/** Type members that should be statically imported. */
@VisibleForTesting

View File

@@ -0,0 +1,70 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static 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.Fix;
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 tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags calls to {@link String#toLowerCase()} and {@link
* String#toUpperCase()}, as these methods implicitly rely on the environment's default locale.
*/
// XXX: Also flag `String::toLowerCase` and `String::toUpperCase` method references. For these cases
// the suggested fix should introduce a lambda expression with a parameter of which the name does
// not coincide with the name of an existing variable name. Such functionality should likely be
// introduced in a utility class.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Specify a `Locale` when calling `String#to{Lower,Upper}Case`",
link = BUG_PATTERNS_BASE_URL + "StringCaseLocaleUsage",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class StringCaseLocaleUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DEFAULT_LOCALE_CASE_CONVERSION =
instanceMethod()
.onExactClass(String.class.getName())
.namedAnyOf("toLowerCase", "toUpperCase")
.withNoParameters();
/** Instantiates a new {@link StringCaseLocaleUsage} instance. */
public StringCaseLocaleUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!DEFAULT_LOCALE_CASE_CONVERSION.matches(tree, state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(suggestLocale(tree, "Locale.ROOT", state))
.addFix(suggestLocale(tree, "Locale.getDefault()", state))
.build();
}
private static Fix suggestLocale(MethodInvocationTree tree, String locale, VisitorState state) {
// XXX: The logic that replaces the first parenthesis assumes that `tree` does not have a source
// code representation such as `str.toLowerCase/* Some comment with parens (). */()`. In such a
// case the comment, rather than the method invocation arguments, will be modified. Implement a
// generic solution for this.
return SuggestedFix.builder()
.addImport("java.util.Locale")
.replace(tree, SourceCode.treeToString(tree, state).replaceFirst("\\(", '(' + locale))
.build();
}
}

View File

@@ -1,9 +1,10 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
@@ -30,8 +31,8 @@ import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@link String#format(String, Object...)} invocations which can
* be replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
* A {@link BugChecker} that flags {@link String#format(String, Object...)} invocations which can be
* replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
* String#valueOf} invocation.
*/
// XXX: What about `v1 + "sep" + v2` and similar expressions? Do we want to rewrite those to
@@ -40,7 +41,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `String#join` over `String#format`",
linkType = NONE,
link = BUG_PATTERNS_BASE_URL + "StringJoin",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class StringJoin extends BugChecker implements MethodInvocationTreeMatcher {

View File

@@ -26,6 +26,9 @@ import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
/** A {@link BugChecker} that flags illegal time-zone related operations. */
@AutoService(BugChecker.class)
@@ -58,7 +61,10 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
.onClassAny(
LocalDate.class.getName(),
LocalDateTime.class.getName(),
LocalTime.class.getName())
LocalTime.class.getName(),
OffsetDateTime.class.getName(),
OffsetTime.class.getName(),
ZonedDateTime.class.getName())
.named("now"),
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());

View File

@@ -0,0 +1,49 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
/**
* A collection of helper methods for working with the AST.
*
* <p>These methods are additions to the ones found in {@link
* com.google.errorprone.util.ASTHelpers}.
*/
public final class MoreASTHelpers {
private MoreASTHelpers() {}
/**
* Finds methods with the specified name in given the {@link VisitorState}'s current enclosing
* class.
*
* @param methodName The method name to search for.
* @param state The {@link VisitorState} from which to derive the enclosing class of interest.
* @return The {@link MethodTree}s of the methods with the given name in the enclosing class.
*/
public static ImmutableList<MethodTree> findMethods(CharSequence methodName, VisitorState state) {
ClassTree clazz = state.findEnclosing(ClassTree.class);
checkArgument(clazz != null, "Visited node is not enclosed by a class");
return clazz.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(method -> method.getName().contentEquals(methodName))
.collect(toImmutableList());
}
/**
* Determines whether there are any methods with the specified name in given the {@link
* VisitorState}'s current enclosing class.
*
* @param methodName The method name to search for.
* @param state The {@link VisitorState} from which to derive the enclosing class of interest.
* @return Whether there are any methods with the given name in the enclosing class.
*/
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}
}

View File

@@ -0,0 +1,81 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.Objects.requireNonNullElse;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.matchers.AnnotationMatcherUtils;
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.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import org.jspecify.nullness.Nullable;
/**
* A collection of JUnit-specific helper methods and {@link Matcher}s.
*
* <p>These constants and methods are additions to the ones found in {@link
* com.google.errorprone.matchers.JUnitMatchers}.
*/
public final class MoreJUnitMatchers {
/** Matches JUnit Jupiter test methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
/** Matches JUnit Jupiter setup and teardown methods. */
public static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
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")));
/**
* Matches methods that have a {@link org.junit.jupiter.params.provider.MethodSource} annotation.
*/
public static final MultiMatcher<MethodTree, AnnotationTree> HAS_METHOD_SOURCE =
annotations(AT_LEAST_ONE, isType("org.junit.jupiter.params.provider.MethodSource"));
private MoreJUnitMatchers() {}
/**
* Returns the names of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method names.
* @return One or more value factory names.
*/
static ImmutableSet<String> getMethodSourceFactoryNames(
AnnotationTree methodSourceAnnotation, MethodTree method) {
String methodName = method.getName().toString();
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value");
if (!(value instanceof NewArrayTree)) {
return ImmutableSet.of(toMethodSourceFactoryName(value, methodName));
}
return ((NewArrayTree) value)
.getInitializers().stream()
.map(name -> toMethodSourceFactoryName(name, methodName))
.collect(toImmutableSet());
}
private static String toMethodSourceFactoryName(
@Nullable ExpressionTree tree, String annotatedMethodName) {
return requireNonNullElse(
Strings.emptyToNull(ASTHelpers.constValue(tree, String.class)), annotatedMethodName);
}
}

View File

@@ -0,0 +1,39 @@
package tech.picnic.errorprone.bugpatterns.util;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.tools.javac.code.Symbol;
/**
* A collection of general-purpose {@link Matcher}s.
*
* <p>These methods are additions to the ones found in {@link Matchers}.
*/
public final class MoreMatchers {
private MoreMatchers() {}
/**
* Returns a {@link Matcher} that determines whether a given {@link AnnotationTree} has a
* meta-annotation of the specified type.
*
* @param <T> The type of tree to match against.
* @param annotationType The binary type name of the annotation (e.g.
* "org.jspecify.annotations.Nullable", or "some.package.OuterClassName$InnerClassName")
* @return A {@link Matcher} that matches trees with the specified meta-annotation.
*/
public static <T extends AnnotationTree> Matcher<T> hasMetaAnnotation(String annotationType) {
TypePredicate typePredicate = hasAnnotation(annotationType);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Consider moving to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -0,0 +1,132 @@
package tech.picnic.errorprone.bugpatterns.util;
import static java.util.stream.Collectors.toCollection;
import com.google.errorprone.VisitorState;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
/**
* A set of helper methods which together define a DSL for defining {@link Type types}.
*
* <p>These methods are meant to be statically imported. Example usage:
*
* <pre>{@code
* Supplier<Type> type =
* VisitorState.memoize(
* generic(
* type("reactor.core.publisher.Flux"),
* subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
* }</pre>
*
* This statement produces a memoized supplier of the type {@code Flux<? extends Publisher<?>>}.
*/
public final class MoreTypes {
private MoreTypes() {}
/**
* Creates a supplier of the type with the given fully qualified name.
*
* <p>This method should only be used when building more complex types in combination with other
* {@link MoreTypes} methods. In other cases prefer directly calling {@link
* Suppliers#typeFromString(String)}.
*
* @param typeName The type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> type(String typeName) {
return Suppliers.typeFromString(typeName);
}
/**
* Creates a supplier of the described generic type.
*
* @param type The base type of interest.
* @param typeArgs The desired type arguments.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
// XXX: The given `type` should be a generic type, so perhaps `withParams` would be a better
// method name. But the DSL wouldn't look as nice that way.
@SafeVarargs
@SuppressWarnings("varargs")
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
return propagateNull(
type,
(state, baseType) -> {
List<Type> params =
Arrays.stream(typeArgs).map(s -> s.get(state)).collect(toCollection(ArrayList::new));
if (params.stream().anyMatch(Objects::isNull)) {
return null;
}
return state.getType(baseType, /* isArray= */ false, params);
});
}
/**
* Creates a raw (erased, non-generic) variant of the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> raw(Supplier<Type> type) {
return propagateNull(type, (state, baseType) -> baseType.tsym.erasure(state.getTypes()));
}
/**
* Creates a {@code ? super T} wildcard type, with {@code T} bound to the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> superOf(Supplier<Type> type) {
return propagateNull(
type,
(state, baseType) ->
new Type.WildcardType(baseType, BoundKind.SUPER, state.getSymtab().boundClass));
}
/**
* Creates a {@code ? extends T} wildcard type, with {@code T} bound to the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> subOf(Supplier<Type> type) {
return propagateNull(
type,
(state, baseType) ->
new Type.WildcardType(
type.get(state), BoundKind.EXTENDS, state.getSymtab().boundClass));
}
/**
* Creates an unbound wildcard type ({@code ?}).
*
* @return A supplier which returns the described type.
*/
public static Supplier<Type> unbound() {
return state ->
new Type.WildcardType(
state.getSymtab().objectType, BoundKind.UNBOUND, state.getSymtab().boundClass);
}
private static Supplier<Type> propagateNull(
Supplier<Type> type, BiFunction<VisitorState, Type, Type> transformer) {
return state ->
Optional.ofNullable(type.get(state)).map(t -> transformer.apply(state, t)).orElse(null);
}
}

View File

@@ -1,14 +1,21 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.sun.tools.javac.util.Position.NOPOS;
import com.google.common.base.CharMatcher;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
* AST nodes.
*/
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
public final class SourceCode {
/** The complement of {@link CharMatcher#whitespace()}. */
private static final CharMatcher NON_WHITESPACE_MATCHER = CharMatcher.whitespace().negate();
private SourceCode() {}
/**
@@ -24,4 +31,32 @@ public final class SourceCode {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
/**
* Creates a {@link SuggestedFix} for the deletion of the given {@link Tree}, including any
* whitespace that follows it.
*
* <p>Removing trailing whitespace may prevent the introduction of an empty line at the start of a
* code block; such empty lines are not removed when formatting the code using Google Java Format.
*
* @param tree The AST node of interest.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
* found.
* @return A non-{@code null} {@link SuggestedFix} similar to one produced by {@link
* SuggestedFix#delete(Tree)}.
*/
public static SuggestedFix deleteWithTrailingWhitespace(Tree tree, VisitorState state) {
CharSequence sourceCode = state.getSourceCode();
int endPos = state.getEndPosition(tree);
if (sourceCode == null || endPos == NOPOS) {
/* We can't identify the trailing whitespace; delete just the tree. */
return SuggestedFix.delete(tree);
}
int whitespaceEndPos = NON_WHITESPACE_MATCHER.indexIn(sourceCode, endPos);
return SuggestedFix.replace(
((DiagnosticPosition) tree).getStartPosition(),
whitespaceEndPos == -1 ? sourceCode.length() : whitespaceEndPos,
"");
}
}

View File

@@ -0,0 +1,105 @@
package tech.picnic.errorprone.bugpatterns.util;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.suppliers.Supplier;
import com.sun.tools.javac.code.ClassFinder;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.util.Name;
/**
* Utility class that helps decide whether it is appropriate to introduce references to (well-known)
* third-party libraries.
*
* <p>This class should be used by {@link BugChecker}s that may otherwise suggest the introduction
* of code that depends on possibly-not-present third-party libraries.
*/
// XXX: Consider giving users more fine-grained control. This would be beneficial in cases where a
// dependency is on the classpath, but new usages are undesirable.
public enum ThirdPartyLibrary {
/**
* AssertJ.
*
* @see <a href="https://assertj.github.io/doc">AssertJ documentation</a>
*/
ASSERTJ("org.assertj.core.api.Assertions"),
/**
* Google's Guava.
*
* @see <a href="https://github.com/google/guava">Guava on GitHub</a>
*/
GUAVA("com.google.common.collect.ImmutableList"),
/**
* New Relic's Java agent API.
*
* @see <a href="https://github.com/newrelic/newrelic-java-agent/tree/main/newrelic-api">New Relic
* Java agent API on GitHub</a>
*/
NEW_RELIC_AGENT_API("com.newrelic.api.agent.Agent"),
/**
* VMWare's Project Reactor.
*
* @see <a href="https://projectreactor.io">Home page</a>
*/
REACTOR("reactor.core.publisher.Flux");
private static final String IGNORE_CLASSPATH_COMPAT_FLAG =
"ErrorProneSupport:IgnoreClasspathCompat";
@SuppressWarnings("ImmutableEnumChecker" /* Supplier is deterministic. */)
private final Supplier<Boolean> canUse;
/**
* Instantiates a {@link ThirdPartyLibrary} enum value.
*
* @param witnessFqcn The fully-qualified class name of a type that is expected to be on the
* classpath iff the associated third-party library is on the classpath.
*/
ThirdPartyLibrary(String witnessFqcn) {
this.canUse = VisitorState.memoize(state -> canIntroduceUsage(witnessFqcn, state));
}
/**
* Tells whether it is okay to introduce a dependency on this well-known third party library in
* the given context.
*
* @param state The context under consideration.
* @return {@code true} iff it is okay to assume or create a dependency on this library.
*/
public boolean isIntroductionAllowed(VisitorState state) {
return canUse.get(state);
}
private static boolean canIntroduceUsage(String className, VisitorState state) {
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
}
/**
* Attempts to determine whether a class with the given FQCN is on the classpath.
*
* <p>The {@link VisitorState}'s symbol table is consulted first. If the type has not yet been
* loaded, then an attempt is made to do so.
*/
private static boolean isKnownClass(String className, VisitorState state) {
return state.getTypeFromString(className) != null || canLoadClass(className, state);
}
private static boolean canLoadClass(String className, VisitorState state) {
ClassFinder classFinder = ClassFinder.instance(state.context);
Name binaryName = state.binaryNameFromClassname(className);
try {
classFinder.loadClass(state.getSymtab().unnamedModule, binaryName);
return true;
} catch (CompletionFailure e) {
return false;
}
}
private static boolean shouldIgnoreClasspath(VisitorState state) {
return state
.errorProneOptions()
.getFlags()
.getBoolean(IGNORE_CLASSPATH_COMPAT_FLAG)
.orElse(Boolean.FALSE);
}
}

View File

@@ -1,16 +1,148 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@OnlineDocumentation
final class AssertJMapRules {
private AssertJMapRules() {}
// XXX: Reduce boilerplate using a `Matcher` that identifies "empty" instances.
static final class AbstractMapAssertIsEmpty<K, V> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
Refaster.anyOf(
mapAssert.containsExactlyEntriesOf(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.isEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.containsOnlyKeys(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
mapAssert.containsExactly(),
mapAssert.containsOnly(),
mapAssert.containsOnlyKeys());
}
@AfterTemplate
void after(AbstractMapAssert<?, ?, K, V> mapAssert) {
mapAssert.isEmpty();
}
}
static final class AssertThatMapIsEmpty<K, V> {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@BeforeTemplate
void before2(Map<K, V> map) {
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).isEmpty();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
}
static final class AbstractMapAssertIsNotEmpty<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>()));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEmpty();
}
}
static final class AssertThatMapIsNotEmpty<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map) {
return Refaster.anyOf(
assertThat(map.isEmpty()).isFalse(),
assertThat(map.size()).isNotEqualTo(0),
assertThat(map.size()).isPositive(),
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).isNotEmpty());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
}
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
@@ -34,4 +166,83 @@ final class AssertJMapRules {
return mapAssert.containsExactlyEntriesOf(ImmutableMap.of(key, value));
}
}
static final class AssertThatMapHasSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map, int length) {
return Refaster.anyOf(
assertThat(map.size()).isEqualTo(length),
assertThat(Refaster.anyOf(map.keySet(), map.values(), map.entrySet())).hasSize(length));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
}
static final class AbstractMapAssertHasSameSizeAs<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.hasSize(map.size());
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.hasSameSizeAs(map);
}
}
static final class AssertThatMapContainsKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
}
static final class AssertThatMapDoesNotContainKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
}
static final class AssertThatMapContainsValue<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {
return assertThat(map.containsValue(value)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, V value) {
return assertThat(map).containsValue(value);
}
}
static final class AssertThatMapDoesNotContainValue<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {
return assertThat(map.containsValue(value)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, V value) {
return assertThat(map).doesNotContainValue(value);
}
}
}

View File

@@ -3,12 +3,9 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
@@ -22,9 +19,7 @@ import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -32,19 +27,16 @@ import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
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;
import org.assertj.core.api.AbstractLongAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.IterableAssert;
import org.assertj.core.api.ListAssert;
import org.assertj.core.api.MapAssert;
@@ -566,173 +558,8 @@ final class AssertJRules {
// Map
//
static final class AssertThatMapIsEmpty<K, V> {
@BeforeTemplate
@SuppressWarnings("unchecked")
void before(AbstractMapAssert<?, ?, K, V> mapAssert) {
Refaster.anyOf(
mapAssert.containsExactlyEntriesOf(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.hasSameSizeAs(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.isEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>())),
mapAssert.containsOnlyKeys(
Refaster.anyOf(
ImmutableList.of(),
new ArrayList<>(),
ImmutableSet.of(),
new HashSet<>(),
new LinkedHashSet<>(),
ImmutableSortedSet.of(),
new TreeSet<>(),
ImmutableMultiset.of(),
ImmutableSortedMultiset.of())),
mapAssert.containsExactly(),
mapAssert.containsOnly(),
mapAssert.containsOnlyKeys());
}
@AfterTemplate
void after(AbstractMapAssert<?, ?, K, V> mapAssert) {
mapAssert.isEmpty();
}
}
// XXX: Find a better name.
static final class AssertThatMapIsEmpty2<K, V> {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@BeforeTemplate
void before2(Map<K, V> map) {
assertThat(Refaster.anyOf(map.keySet(), map.values())).isEmpty();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
}
static final class AssertThatMapIsNotEmpty<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEqualTo(
Refaster.anyOf(
ImmutableMap.of(),
ImmutableBiMap.of(),
ImmutableSortedMap.of(),
new HashMap<>(),
new LinkedHashMap<>(),
new TreeMap<>()));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert) {
return mapAssert.isNotEmpty();
}
}
// XXX: Find a better name.
static final class AssertThatMapIsNotEmpty2<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map) {
return Refaster.anyOf(
assertThat(map.isEmpty()).isFalse(),
assertThat(map.size()).isNotEqualTo(0),
assertThat(map.size()).isPositive(),
assertThat(Refaster.anyOf(map.keySet(), map.values())).isNotEmpty());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
}
static final class AssertThatMapHasSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map, int length) {
return Refaster.anyOf(
assertThat(map.size()).isEqualTo(length),
assertThat(Refaster.anyOf(map.keySet(), map.values())).hasSize(length));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
}
static final class AssertThatMapsHaveSameSize<K, V> {
@BeforeTemplate
AbstractAssert<?, ?> before(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1)
.hasSize(Refaster.anyOf(map2.size(), map2.keySet().size(), map2.values().size()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1).hasSameSizeAs(map2);
}
}
// XXX: Should also add a rule (elsewhere) to simplify `map.keySet().contains(key)`.
static final class AssertThatMapContainsKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
}
static final class AssertThatMapDoesNotContainKey<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, K key) {
return assertThat(map.containsKey(key)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
}
// XXX: To match in all cases there'll need to be a `@BeforeTemplate` variant for each
// `assertThat` overload. Consider defining a `BugChecker` instead.
static final class AssertThatMapContainsEntry<K, V> {
@BeforeTemplate
ObjectAssert<?> before(Map<K, V> map, K key, V value) {

View File

@@ -19,11 +19,8 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
@@ -73,32 +70,6 @@ final class AssortedRules {
}
}
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
// not allow an empty non-EnumMap to be provided.
static final class CreateEnumMap<K extends Enum<K>, V> {
@BeforeTemplate
Map<K, V> before() {
return new HashMap<>();
}
@AfterTemplate
Map<K, V> after() {
return new EnumMap<>(Refaster.<K>clazz());
}
}
static final class MapGetOrNull<K, V, L> {
@BeforeTemplate
@Nullable V before(Map<K, V> map, L key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable V after(Map<K, V> map, L key) {
return map.get(key);
}
}
/**
* Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link
* ImmutableSet#toImmutableSet()} and produces a more compact object.
@@ -224,32 +195,6 @@ final class AssortedRules {
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapKeyStream<K, V> {
@BeforeTemplate
Stream<K> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getKey);
}
@AfterTemplate
Stream<K> after(Map<K, V> map) {
return map.keySet().stream();
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapValueStream<K, V> {
@BeforeTemplate
Stream<V> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getValue);
}
@AfterTemplate
Stream<V> after(Map<K, V> map) {
return map.values().stream();
}
}
/** Prefer {@link Splitter#splitToStream(CharSequence)} over less efficient alternatives. */
static final class SplitToStream {
@BeforeTemplate

View File

@@ -50,17 +50,18 @@ final class BigDecimalRules {
}
}
/** Prefer {@link BigDecimal#valueOf(long)} over the associated constructor. */
// XXX: Ideally we'd also rewrite `BigDecimal.valueOf("<some-integer-value>")`, but it doesn't
// appear that's currently possible with Error Prone.
static final class BigDecimalFactoryMethod {
/** Prefer {@link BigDecimal#valueOf(double)} over the associated constructor. */
// XXX: Ideally we also rewrite `new BigDecimal("<some-integer-value>")` in cases where the
// specified number can be represented as an `int` or `long`, but that requires a custom
// `BugChecker`.
static final class BigDecimalValueOf {
@BeforeTemplate
BigDecimal before(long value) {
BigDecimal before(double value) {
return new BigDecimal(value);
}
@AfterTemplate
BigDecimal after(long value) {
BigDecimal after(double value) {
return BigDecimal.valueOf(value);
}
}

View File

@@ -17,6 +17,7 @@ import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -404,6 +405,19 @@ final class CollectionRules {
}
}
/** Prefer {@link Collection#forEach(Consumer)} over more contrived alternatives. */
static final class CollectionForEach<T> {
@BeforeTemplate
void before(Collection<T> collection, Consumer<? super T> consumer) {
collection.stream().forEach(consumer);
}
@AfterTemplate
void after(Collection<T> collection, Consumer<? super T> consumer) {
collection.forEach(consumer);
}
}
// XXX: collection.stream().noneMatch(e -> e.equals(other))
// ^ This is !collection.contains(other). Do we already rewrite variations on this?
}

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collections;
@@ -24,6 +25,7 @@ import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Comparator}s. */
@@ -37,7 +39,10 @@ final class ComparatorRules {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
T::compareTo,
comparing(Refaster.anyOf(identity(), v -> v)),
Collections.<T>reverseOrder(reverseOrder()),
Comparator.<T>reverseOrder().reversed());
}
@AfterTemplate
@@ -51,11 +56,16 @@ final class ComparatorRules {
static final class ReverseOrder<T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<T> before() {
return Comparator.<T>naturalOrder().reversed();
return Refaster.anyOf(
Collections.reverseOrder(),
Collections.reverseOrder(T::compareTo),
Collections.<T>reverseOrder(naturalOrder()),
Comparator.<T>naturalOrder().reversed());
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` if/when
// https://github.com/google/error-prone/pull/3584 is merged and released.
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return reverseOrder();
}
@@ -189,15 +199,54 @@ final class ComparatorRules {
}
}
/** Prefer {@link Comparable#compareTo(Object)}} over more verbose alternatives. */
static final class CompareTo<T extends Comparable<? super T>> {
@BeforeTemplate
int before(T value1, T value2) {
return Refaster.anyOf(
Comparator.<T>naturalOrder().compare(value1, value2),
Comparator.<T>reverseOrder().compare(value2, value1));
}
@AfterTemplate
int after(T value1, T value2) {
return value1.compareTo(value2);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class MinOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.min(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#min(Comparable, Comparable)}} over more verbose alternatives. */
static final class MinOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
return Refaster.anyOf(
value1.compareTo(value2) <= 0 ? value1 : value2,
value1.compareTo(value2) > 0 ? value2 : value1,
value2.compareTo(value1) < 0 ? value2 : value1,
value2.compareTo(value1) >= 0 ? value1 : value2,
Comparators.min(value1, value2, naturalOrder()),
Comparators.max(value1, value2, reverseOrder()),
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
}
@AfterTemplate
@@ -212,12 +261,17 @@ final class ComparatorRules {
static final class MinOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
return Refaster.anyOf(
cmp.compare(value1, value2) <= 0 ? value1 : value2,
cmp.compare(value1, value2) > 0 ? value2 : value1,
cmp.compare(value2, value1) < 0 ? value2 : value1,
cmp.compare(value2, value1) >= 0 ? value1 : value2,
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
}
@AfterTemplate
@@ -226,15 +280,39 @@ final class ComparatorRules {
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class MaxOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.max(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#max(Comparable, Comparable)}} over more verbose alternatives. */
static final class MaxOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
return Refaster.anyOf(
value1.compareTo(value2) >= 0 ? value1 : value2,
value1.compareTo(value2) < 0 ? value2 : value1,
value2.compareTo(value1) > 0 ? value2 : value1,
value2.compareTo(value1) <= 0 ? value1 : value2,
Comparators.max(value1, value2, naturalOrder()),
Comparators.min(value1, value2, reverseOrder()),
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
}
@AfterTemplate
@@ -249,12 +327,17 @@ final class ComparatorRules {
static final class MaxOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
return Refaster.anyOf(
cmp.compare(value1, value2) >= 0 ? value1 : value2,
cmp.compare(value1, value2) < 0 ? value2 : value1,
cmp.compare(value2, value1) > 0 ? value2 : value1,
cmp.compare(value2, value1) <= 0 ? value1 : value2,
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
}
@AfterTemplate

View File

@@ -0,0 +1,121 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Map} instances. */
@OnlineDocumentation
final class MapRules {
private MapRules() {}
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
// not allow an empty non-EnumMap to be provided.
static final class CreateEnumMap<K extends Enum<K>, V> {
@BeforeTemplate
Map<K, V> before() {
return new HashMap<>();
}
@AfterTemplate
Map<K, V> after() {
return new EnumMap<>(Refaster.<K>clazz());
}
}
static final class MapGetOrNull<K, V, T> {
@BeforeTemplate
@Nullable V before(Map<K, V> map, T key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable V after(Map<K, V> map, T key) {
return map.get(key);
}
}
/** Prefer {@link Map#isEmpty()} over more contrived alternatives. */
static final class MapIsEmpty<K, V> {
@BeforeTemplate
boolean before(Map<K, V> map) {
return Refaster.anyOf(map.keySet(), map.values(), map.entrySet()).isEmpty();
}
@AfterTemplate
boolean after(Map<K, V> map) {
return map.isEmpty();
}
}
/** Prefer {@link Map#size()} over more contrived alternatives. */
static final class MapSize<K, V> {
@BeforeTemplate
int before(Map<K, V> map) {
return Refaster.anyOf(map.keySet(), map.values(), map.entrySet()).size();
}
@AfterTemplate
int after(Map<K, V> map) {
return map.size();
}
}
/** Prefer {@link Map#containsKey(Object)} over more contrived alternatives. */
static final class MapContainsKey<K, V, T> {
@BeforeTemplate
boolean before(Map<K, V> map, T key) {
return map.keySet().contains(key);
}
@AfterTemplate
boolean after(Map<K, V> map, T key) {
return map.containsKey(key);
}
}
/** Prefer {@link Map#containsValue(Object)} over more contrived alternatives. */
static final class MapContainsValue<K, V, T> {
@BeforeTemplate
boolean before(Map<K, V> map, T value) {
return map.values().contains(value);
}
@AfterTemplate
boolean after(Map<K, V> map, T value) {
return map.containsValue(value);
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapKeyStream<K, V> {
@BeforeTemplate
Stream<K> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getKey);
}
@AfterTemplate
Stream<K> after(Map<K, V> map) {
return map.keySet().stream();
}
}
/** Don't unnecessarily use {@link Map#entrySet()}. */
static final class MapValueStream<K, V> {
@BeforeTemplate
Stream<V> before(Map<K, V> map) {
return map.entrySet().stream().map(Map.Entry::getValue);
}
@AfterTemplate
Stream<V> after(Map<K, V> map) {
return map.values().stream();
}
}
}

View File

@@ -355,6 +355,42 @@ final class OptionalRules {
}
}
/**
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when filtering a value of the
* former type.
*/
static final class OptionalFilter<T> {
@BeforeTemplate
Optional<T> before(Optional<T> optional, Predicate<? super T> predicate) {
return Refaster.anyOf(
optional.stream().filter(predicate).findFirst(),
optional.stream().filter(predicate).findAny());
}
@AfterTemplate
Optional<T> after(Optional<T> optional, Predicate<? super T> predicate) {
return optional.filter(predicate);
}
}
/**
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when mapping a value of the
* former type.
*/
// XXX: If `StreamMapFirst` also simplifies `.findAny()` expressions, then this rule can be
// dropped in favour of `StreamMapFirst` and `OptionalIdentity`.
static final class OptionalMap<S, T> {
@BeforeTemplate
Optional<? extends T> before(Optional<S> optional, Function<? super S, ? extends T> function) {
return optional.stream().map(function).findAny();
}
@AfterTemplate
Optional<? extends T> after(Optional<S> optional, Function<? super S, ? extends T> function) {
return optional.map(function);
}
}
// XXX: Add a rule for:
// `optional.flatMap(x -> pred(x) ? Optional.empty() : Optional.of(x))` and variants.
// (Maybe canonicalize the inner expression. Maybe we rewrite already.)

View File

@@ -1,9 +1,11 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.MoreCollectors.toOptional;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MoreCollectors;
@@ -18,17 +20,23 @@ import java.time.Duration;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jspecify.nullness.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.publisher.PublisherProbe;
import reactor.util.context.Context;
import reactor.util.function.Tuple2;
import tech.picnic.errorprone.refaster.annotation.Description;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.annotation.Severity;
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;
/** Refaster rules related to Reactor expressions and statements. */
@@ -52,6 +60,45 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#empty()} over more contrived alternatives. */
static final class MonoEmpty<T> {
@BeforeTemplate
Mono<T> before() {
return Refaster.anyOf(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
@AfterTemplate
Mono<T> after() {
return Mono.empty();
}
}
/** Prefer {@link Mono#just(Object)} over more contrived alternatives. */
static final class MonoJust<T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.of(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.just(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Object)} over more contrived alternatives. */
static final class MonoJustOrEmpty<@Nullable T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.ofNullable(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.justOrEmpty(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Optional)} over more verbose alternatives. */
// XXX: If `optional` is a constant and effectively-final expression then the `Mono.defer` can be
// dropped. Should look into Refaster support for identifying this.
@@ -71,6 +118,125 @@ final class ReactorRules {
}
}
/**
* Try to avoid expressions of type {@code Optional<Mono<T>>}, but if you must map an {@link
* Optional} to this type, prefer using {@link Mono#just(Object)}.
*/
static final class OptionalMapMonoJust<T> {
@BeforeTemplate
Optional<Mono<T>> before(Optional<T> optional) {
return optional.map(Mono::justOrEmpty);
}
@AfterTemplate
Optional<Mono<T>> after(Optional<T> optional) {
return optional.map(Mono::just);
}
}
/**
* Prefer a {@link Mono#justOrEmpty(Optional)} and {@link Mono#switchIfEmpty(Mono)} chain over
* more contrived alternatives.
*
* <p>In particular, avoid mixing of the {@link Optional} and {@link Mono} APIs.
*/
static final class MonoFromOptionalSwitchIfEmpty<T> {
@BeforeTemplate
Mono<T> before(Optional<T> optional, Mono<T> mono) {
return optional.map(Mono::just).orElse(mono);
}
@AfterTemplate
Mono<T> after(Optional<T> optional, Mono<T> mono) {
return Mono.justOrEmpty(optional).switchIfEmpty(mono);
}
}
/**
* Prefer {@link Mono#zip(Mono, Mono)} over a chained {@link Mono#zipWith(Mono)}, as the former
* better conveys that the {@link Mono}s may be subscribed to concurrently, and generalizes to
* combining three or more reactive streams.
*/
static final class MonoZip<T, S> {
@BeforeTemplate
Mono<Tuple2<T, S>> before(Mono<T> mono, Mono<S> other) {
return mono.zipWith(other);
}
@AfterTemplate
Mono<Tuple2<T, S>> after(Mono<T> mono, Mono<S> other) {
return Mono.zip(mono, other);
}
}
/**
* Prefer {@link Mono#zip(Mono, Mono)} with a chained combinator over a chained {@link
* Mono#zipWith(Mono, BiFunction)}, as the former better conveys that the {@link Mono}s may be
* subscribed to concurrently, and generalizes to combining three or more reactive streams.
*/
static final class MonoZipWithCombinator<T, S, R> {
@BeforeTemplate
Mono<R> before(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
return mono.zipWith(other, combinator);
}
@AfterTemplate
Mono<R> after(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
return Mono.zip(mono, other).map(function(combinator));
}
}
/**
* Prefer {@link Flux#zip(Publisher, Publisher)} over a chained {@link Flux#zipWith(Publisher)},
* as the former better conveys that the {@link Publisher}s may be subscribed to concurrently, and
* generalizes to combining three or more reactive streams.
*/
static final class FluxZip<T, S> {
@BeforeTemplate
Flux<Tuple2<T, S>> before(Flux<T> flux, Publisher<S> other) {
return flux.zipWith(other);
}
@AfterTemplate
Flux<Tuple2<T, S>> after(Flux<T> flux, Publisher<S> other) {
return Flux.zip(flux, other);
}
}
/**
* Prefer {@link Flux#zip(Publisher, Publisher)} with a chained combinator over a chained {@link
* Flux#zipWith(Publisher, BiFunction)}, as the former better conveys that the {@link Publisher}s
* may be subscribed to concurrently, and generalizes to combining three or more reactive streams.
*/
static final class FluxZipWithCombinator<T, S, R> {
@BeforeTemplate
Flux<R> before(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
return flux.zipWith(other, combinator);
}
@AfterTemplate
Flux<R> after(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
return Flux.zip(flux, other).map(function(combinator));
}
}
/**
* Prefer {@link Flux#zipWithIterable(Iterable)} with a chained combinator over {@link
* Flux#zipWithIterable(Iterable, BiFunction)}, as the former generally yields more readable code.
*/
static final class FluxZipWithIterable<T, S, R> {
@BeforeTemplate
Flux<R> before(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
return flux.zipWithIterable(iterable, combinator);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Flux<R> after(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
return flux.zipWithIterable(iterable).map(function(combinator));
}
}
/** Don't unnecessarily defer {@link Mono#error(Throwable)}. */
static final class MonoDeferredError<T> {
@BeforeTemplate
@@ -144,6 +310,60 @@ final class ReactorRules {
}
}
/**
* Prefer {@link Flux#take(long, boolean)} over {@link Flux#take(long)}.
*
* <p>In Reactor versions prior to 3.5.0, {@code Flux#take(long)} makes an unbounded request
* upstream, and is equivalent to {@code Flux#take(long, false)}. In 3.5.0, the behavior of {@code
* Flux#take(long)} will change to that of {@code Flux#take(long, true)}.
*
* <p>The intent with this Refaster rule is to get the new behavior before upgrading to Reactor
* 3.5.0.
*/
// XXX: Drop this rule some time after upgrading to Reactor 3.6.0, or introduce a way to apply
// this rule only when an older version of Reactor is on the classpath.
// XXX: Once Reactor 3.6.0 is out, introduce a rule that rewrites code in the opposite direction.
@Description(
"Prior to Reactor 3.5.0, `take(n)` requests and unbounded number of elements upstream.")
@Severity(WARNING)
static final class FluxTake<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, long n) {
return flux.take(n);
}
@AfterTemplate
Flux<T> after(Flux<T> flux, long n) {
return flux.take(n, /* limitRequest= */ true);
}
}
/** Prefer {@link Mono#defaultIfEmpty(Object)} over more contrived alternatives. */
static final class MonoDefaultIfEmpty<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono, T object) {
return mono.switchIfEmpty(Mono.just(object));
}
@AfterTemplate
Mono<T> after(Mono<T> mono, T object) {
return mono.defaultIfEmpty(object);
}
}
/** Prefer {@link Flux#defaultIfEmpty(Object)} over more contrived alternatives. */
static final class FluxDefaultIfEmpty<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, T object) {
return flux.switchIfEmpty(Refaster.anyOf(Mono.just(object), Flux.just(object)));
}
@AfterTemplate
Flux<T> after(Flux<T> flux, T object) {
return flux.defaultIfEmpty(object);
}
}
/** Don't unnecessarily pass an empty publisher to {@link Mono#switchIfEmpty(Mono)}. */
static final class MonoSwitchIfEmptyOfEmptyPublisher<T> {
@BeforeTemplate
@@ -174,7 +394,10 @@ final class ReactorRules {
static final class FluxConcatMap<T, S> {
@BeforeTemplate
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
return Refaster.anyOf(flux.flatMap(function, 1), flux.flatMapSequential(function, 1));
return Refaster.anyOf(
flux.flatMap(function, 1),
flux.flatMapSequential(function, 1),
flux.map(function).concatMap(identity()));
}
@AfterTemplate
@@ -191,7 +414,9 @@ final class ReactorRules {
Function<? super T, ? extends Publisher<? extends S>> function,
int prefetch) {
return Refaster.anyOf(
flux.flatMap(function, 1, prefetch), flux.flatMapSequential(function, 1, prefetch));
flux.flatMap(function, 1, prefetch),
flux.flatMapSequential(function, 1, prefetch),
flux.map(function).concatMap(identity(), prefetch));
}
@AfterTemplate
@@ -243,16 +468,195 @@ final class ReactorRules {
*/
abstract static class MonoFlatMapToFlux<T, S> {
@Placeholder(allowsIdentity = true)
abstract Mono<S> valueTransformation(@MayOptionallyUse T value);
abstract Mono<S> transformation(@MayOptionallyUse T value);
@BeforeTemplate
Flux<S> before(Mono<T> mono) {
return mono.flatMapMany(v -> valueTransformation(v));
return mono.flatMapMany(v -> transformation(v));
}
@AfterTemplate
Flux<S> after(Mono<T> mono) {
return mono.flatMap(v -> valueTransformation(v)).flux();
return mono.flatMap(v -> transformation(v)).flux();
}
}
/**
* Prefer {@link Mono#map(Function)} over alternatives that unnecessarily require an inner
* subscription.
*/
abstract static class MonoMap<T, S> {
@Placeholder(allowsIdentity = true)
abstract S transformation(@MayOptionallyUse T value);
@BeforeTemplate
Mono<S> before(Mono<T> mono) {
return mono.flatMap(x -> Mono.just(transformation(x)));
}
@AfterTemplate
Mono<S> after(Mono<T> mono) {
return mono.map(x -> transformation(x));
}
}
/**
* Prefer {@link Flux#map(Function)} over alternatives that unnecessarily require an inner
* subscription.
*/
abstract static class FluxMap<T, S> {
@Placeholder(allowsIdentity = true)
abstract S transformation(@MayOptionallyUse T value);
@BeforeTemplate
Flux<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
return Refaster.anyOf(
flux.concatMap(x -> Mono.just(transformation(x))),
flux.concatMap(x -> Flux.just(transformation(x))),
flux.concatMap(x -> Mono.just(transformation(x)), prefetch),
flux.concatMap(x -> Flux.just(transformation(x)), prefetch),
flux.concatMapDelayError(x -> Mono.just(transformation(x))),
flux.concatMapDelayError(x -> Flux.just(transformation(x))),
flux.concatMapDelayError(x -> Mono.just(transformation(x)), prefetch),
flux.concatMapDelayError(x -> Flux.just(transformation(x)), prefetch),
flux.concatMapDelayError(x -> Mono.just(transformation(x)), delayUntilEnd, prefetch),
flux.concatMapDelayError(x -> Flux.just(transformation(x)), delayUntilEnd, prefetch),
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency),
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency),
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapDelayError(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapDelayError(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency),
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency),
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapSequentialDelayError(
x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
flux.flatMapSequentialDelayError(
x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
flux.switchMap(x -> Mono.just(transformation(x))),
flux.switchMap(x -> Flux.just(transformation(x))));
}
@AfterTemplate
Flux<S> after(Flux<T> flux) {
return flux.map(x -> transformation(x));
}
}
/**
* Prefer {@link Mono#mapNotNull(Function)} over alternatives that unnecessarily require an inner
* subscription.
*/
abstract static class MonoMapNotNull<T, S> {
@Placeholder(allowsIdentity = true)
abstract S transformation(@MayOptionallyUse T value);
@BeforeTemplate
Mono<S> before(Mono<T> mono) {
return mono.flatMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)), Mono.fromSupplier(() -> transformation(x))));
}
@AfterTemplate
Mono<S> after(Mono<T> mono) {
return mono.mapNotNull(x -> transformation(x));
}
}
/**
* Prefer {@link Flux#mapNotNull(Function)} over alternatives that unnecessarily require an inner
* subscription.
*/
abstract static class FluxMapNotNull<T, S> {
@Placeholder(allowsIdentity = true)
abstract S transformation(@MayOptionallyUse T value);
@BeforeTemplate
Publisher<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
return Refaster.anyOf(
flux.concatMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x)))),
flux.concatMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
prefetch),
flux.concatMapDelayError(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x)))),
flux.concatMapDelayError(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
prefetch),
flux.concatMapDelayError(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
delayUntilEnd,
prefetch),
flux.flatMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency),
flux.flatMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency,
prefetch),
flux.flatMapDelayError(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency,
prefetch),
flux.flatMapSequential(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency),
flux.flatMapSequential(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency,
prefetch),
flux.flatMapSequentialDelayError(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x))),
maxConcurrency,
prefetch),
flux.switchMap(
x ->
Refaster.anyOf(
Mono.justOrEmpty(transformation(x)),
Mono.fromSupplier(() -> transformation(x)))));
}
@AfterTemplate
Flux<S> after(Flux<T> flux) {
return flux.mapNotNull(x -> transformation(x));
}
}
@@ -260,7 +664,8 @@ final class ReactorRules {
static final class MonoFlux<T> {
@BeforeTemplate
Flux<T> before(Mono<T> mono) {
return Flux.concat(mono);
return Refaster.anyOf(
mono.flatMapMany(Mono::just), mono.flatMapMany(Flux::just), Flux.concat(mono));
}
@AfterTemplate
@@ -277,9 +682,7 @@ final class ReactorRules {
static final class MonoCollectToOptional<T> {
@BeforeTemplate
Mono<Optional<T>> before(Mono<T> mono) {
return Refaster.anyOf(
mono.map(Optional::of).defaultIfEmpty(Optional.empty()),
mono.map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
return mono.map(Optional::of).defaultIfEmpty(Optional.empty());
}
@AfterTemplate
@@ -315,6 +718,32 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#flatMap(Function)} over more contrived alternatives. */
static final class MonoFlatMap<S, T> {
@BeforeTemplate
Mono<T> before(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
return mono.map(function).flatMap(identity());
}
@AfterTemplate
Mono<T> after(Mono<S> mono, Function<? super S, ? extends Mono<? extends T>> function) {
return mono.flatMap(function);
}
}
/** Prefer {@link Mono#flatMapMany(Function)} over more contrived alternatives. */
static final class MonoFlatMapMany<S, T> {
@BeforeTemplate
Flux<T> before(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
return mono.map(function).flatMapMany(identity());
}
@AfterTemplate
Flux<T> after(Mono<S> mono, Function<? super S, ? extends Publisher<? extends T>> function) {
return mono.flatMapMany(function);
}
}
/**
* Prefer {@link Flux#concatMapIterable(Function)} over alternatives that require an additional
* subscription.
@@ -352,6 +781,42 @@ final class ReactorRules {
}
}
/**
* Prefer {@link Mono#doOnError(Class, Consumer)} over {@link Mono#doOnError(Predicate, Consumer)}
* where possible.
*/
static final class MonoDoOnError<T> {
@BeforeTemplate
Mono<T> before(
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
return mono.doOnError(clazz::isInstance, onError);
}
@AfterTemplate
Mono<T> after(
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
return mono.doOnError(clazz, onError);
}
}
/**
* Prefer {@link Flux#doOnError(Class, Consumer)} over {@link Flux#doOnError(Predicate, Consumer)}
* where possible.
*/
static final class FluxDoOnError<T> {
@BeforeTemplate
Flux<T> before(
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
return flux.doOnError(clazz::isInstance, onError);
}
@AfterTemplate
Flux<T> after(
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
return flux.doOnError(clazz, onError);
}
}
/** Prefer {@link Mono#onErrorComplete()} over more contrived alternatives. */
static final class MonoOnErrorComplete<T> {
@BeforeTemplate
@@ -378,6 +843,225 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#onErrorComplete(Class)}} over more contrived alternatives. */
static final class MonoOnErrorCompleteClass<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz) {
return Refaster.anyOf(
mono.onErrorComplete(clazz::isInstance), mono.onErrorResume(clazz, e -> Mono.empty()));
}
@AfterTemplate
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz) {
return mono.onErrorComplete(clazz);
}
}
/** Prefer {@link Flux#onErrorComplete(Class)}} over more contrived alternatives. */
static final class FluxOnErrorCompleteClass<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz) {
return Refaster.anyOf(
flux.onErrorComplete(clazz::isInstance),
flux.onErrorResume(clazz, e -> Refaster.anyOf(Mono.empty(), Flux.empty())));
}
@AfterTemplate
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz) {
return flux.onErrorComplete(clazz);
}
}
/** Prefer {@link Mono#onErrorComplete(Predicate)}} over more contrived alternatives. */
static final class MonoOnErrorCompletePredicate<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono, Predicate<? super Throwable> predicate) {
return mono.onErrorResume(predicate, e -> Mono.empty());
}
@AfterTemplate
Mono<T> after(Mono<T> mono, Predicate<? super Throwable> predicate) {
return mono.onErrorComplete(predicate);
}
}
/** Prefer {@link Flux#onErrorComplete(Predicate)}} over more contrived alternatives. */
static final class FluxOnErrorCompletePredicate<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, Predicate<? super Throwable> predicate) {
return flux.onErrorResume(predicate, e -> Refaster.anyOf(Mono.empty(), Flux.empty()));
}
@AfterTemplate
Flux<T> after(Flux<T> flux, Predicate<? super Throwable> predicate) {
return flux.onErrorComplete(predicate);
}
}
/**
* Prefer {@link Mono#onErrorContinue(Class, BiConsumer)} over {@link
* Mono#onErrorContinue(Predicate, BiConsumer)} where possible.
*/
static final class MonoOnErrorContinue<T> {
@BeforeTemplate
Mono<T> before(
Mono<T> mono,
Class<? extends Throwable> clazz,
BiConsumer<Throwable, Object> errorConsumer) {
return mono.onErrorContinue(clazz::isInstance, errorConsumer);
}
@AfterTemplate
Mono<T> after(
Mono<T> mono,
Class<? extends Throwable> clazz,
BiConsumer<Throwable, Object> errorConsumer) {
return mono.onErrorContinue(clazz, errorConsumer);
}
}
/**
* Prefer {@link Flux#onErrorContinue(Class, BiConsumer)} over {@link
* Flux#onErrorContinue(Predicate, BiConsumer)} where possible.
*/
static final class FluxOnErrorContinue<T> {
@BeforeTemplate
Flux<T> before(
Flux<T> flux,
Class<? extends Throwable> clazz,
BiConsumer<Throwable, Object> errorConsumer) {
return flux.onErrorContinue(clazz::isInstance, errorConsumer);
}
@AfterTemplate
Flux<T> after(
Flux<T> flux,
Class<? extends Throwable> clazz,
BiConsumer<Throwable, Object> errorConsumer) {
return flux.onErrorContinue(clazz, errorConsumer);
}
}
/**
* Prefer {@link Mono#onErrorMap(Class, Function)} over {@link Mono#onErrorMap(Predicate,
* Function)} where possible.
*/
static final class MonoOnErrorMap<T> {
@BeforeTemplate
Mono<T> before(
Mono<T> mono,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Throwable> mapper) {
return mono.onErrorMap(clazz::isInstance, mapper);
}
@AfterTemplate
Mono<T> after(
Mono<T> mono,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Throwable> mapper) {
return mono.onErrorMap(clazz, mapper);
}
}
/**
* Prefer {@link Flux#onErrorMap(Class, Function)} over {@link Flux#onErrorMap(Predicate,
* Function)} where possible.
*/
static final class FluxOnErrorMap<T> {
@BeforeTemplate
Flux<T> before(
Flux<T> flux,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Throwable> mapper) {
return flux.onErrorMap(clazz::isInstance, mapper);
}
@AfterTemplate
Flux<T> after(
Flux<T> flux,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Throwable> mapper) {
return flux.onErrorMap(clazz, mapper);
}
}
/**
* Prefer {@link Mono#onErrorResume(Class, Function)} over {@link Mono#onErrorResume(Predicate,
* Function)} where possible.
*/
static final class MonoOnErrorResume<T> {
@BeforeTemplate
Mono<T> before(
Mono<T> mono,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
return mono.onErrorResume(clazz::isInstance, fallback);
}
@AfterTemplate
Mono<T> after(
Mono<T> mono,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
return mono.onErrorResume(clazz, fallback);
}
}
/**
* Prefer {@link Flux#onErrorResume(Class, Function)} over {@link Flux#onErrorResume(Predicate,
* Function)} where possible.
*/
static final class FluxOnErrorResume<T> {
@BeforeTemplate
Flux<T> before(
Flux<T> flux,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
return flux.onErrorResume(clazz::isInstance, fallback);
}
@AfterTemplate
Flux<T> after(
Flux<T> flux,
Class<? extends Throwable> clazz,
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
return flux.onErrorResume(clazz, fallback);
}
}
/**
* Prefer {@link Mono#onErrorReturn(Class, Object)} over {@link Mono#onErrorReturn(Predicate,
* Object)} where possible.
*/
static final class MonoOnErrorReturn<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
return mono.onErrorReturn(clazz::isInstance, fallbackValue);
}
@AfterTemplate
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
return mono.onErrorReturn(clazz, fallbackValue);
}
}
/**
* Prefer {@link Flux#onErrorReturn(Class, Object)} over {@link Flux#onErrorReturn(Predicate,
* Object)} where possible.
*/
static final class FluxOnErrorReturn<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
return flux.onErrorReturn(clazz::isInstance, fallbackValue);
}
@AfterTemplate
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
return flux.onErrorReturn(clazz, fallbackValue);
}
}
/** Prefer {@link reactor.util.context.Context#empty()}} over more verbose alternatives. */
// XXX: Consider introducing an `IsEmpty` matcher that identifies a wide range of guaranteed-empty
// `Collection` and `Map` expressions.

View File

@@ -77,6 +77,9 @@ final class StreamRules {
* Prefer {@link Arrays#stream(Object[])} over {@link Stream#of(Object[])}, as the former is
* clearer.
*/
// XXX: Introduce a `Matcher` that identifies `Refaster.asVarargs(...)` invocations and annotate
// the `array` parameter as `@NotMatches(IsRefasterAsVarargs.class)`. Then elsewhere
// `@SuppressWarnings("StreamOfArray")` annotations can be dropped.
static final class StreamOfArray<T> {
@BeforeTemplate
Stream<T> before(T[] array) {
@@ -168,8 +171,9 @@ final class StreamRules {
* Where possible, clarify that a mapping operation will be applied only to a single stream
* element.
*/
// XXX: Consider whether to have a similar rule for `.findAny()`. For parallel streams it
// wouldn't be quite the same....
// XXX: Implement a similar rule for `.findAny()`. For parallel streams this wouldn't be quite the
// same, so such a rule requires a `Matcher` that heuristically identifies `Stream` expressions
// with deterministic order.
// XXX: This change is not equivalent for `null`-returning functions, as the original code throws
// an NPE if the first element is `null`, while the latter yields an empty `Optional`.
static final class StreamMapFirst<T, S> {

View File

@@ -13,9 +13,11 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
@@ -64,7 +66,83 @@ final class TimeRules {
}
}
/** Prefer {@link Instant#atOffset(ZoneOffset)} over the more verbose alternative. */
/** Prefer {@link LocalDate#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
static final class LocalDateOfInstant {
@BeforeTemplate
LocalDate before(Instant instant, ZoneId zoneId) {
return Refaster.anyOf(
instant.atZone(zoneId).toLocalDate(),
LocalDateTime.ofInstant(instant, zoneId).toLocalDate(),
OffsetDateTime.ofInstant(instant, zoneId).toLocalDate());
}
@BeforeTemplate
LocalDate before(Instant instant, ZoneOffset zoneId) {
return instant.atOffset(zoneId).toLocalDate();
}
@AfterTemplate
LocalDate after(Instant instant, ZoneId zoneId) {
return LocalDate.ofInstant(instant, zoneId);
}
}
/** Prefer {@link LocalDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
static final class LocalDateTimeOfInstant {
@BeforeTemplate
LocalDateTime before(Instant instant, ZoneId zoneId) {
return Refaster.anyOf(
instant.atZone(zoneId).toLocalDateTime(),
OffsetDateTime.ofInstant(instant, zoneId).toLocalDateTime());
}
@BeforeTemplate
LocalDateTime before(Instant instant, ZoneOffset zoneId) {
return instant.atOffset(zoneId).toLocalDateTime();
}
@AfterTemplate
LocalDateTime after(Instant instant, ZoneId zoneId) {
return LocalDateTime.ofInstant(instant, zoneId);
}
}
/** Prefer {@link LocalTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
static final class LocalTimeOfInstant {
@BeforeTemplate
LocalTime before(Instant instant, ZoneId zoneId) {
return Refaster.anyOf(
instant.atZone(zoneId).toLocalTime(),
LocalDateTime.ofInstant(instant, zoneId).toLocalTime(),
OffsetDateTime.ofInstant(instant, zoneId).toLocalTime(),
OffsetTime.ofInstant(instant, zoneId).toLocalTime());
}
@BeforeTemplate
LocalTime before(Instant instant, ZoneOffset zoneId) {
return instant.atOffset(zoneId).toLocalTime();
}
@AfterTemplate
LocalTime after(Instant instant, ZoneId zoneId) {
return LocalTime.ofInstant(instant, zoneId);
}
}
/** Prefer {@link OffsetDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
static final class OffsetDateTimeOfInstant {
@BeforeTemplate
OffsetDateTime before(Instant instant, ZoneId zoneId) {
return instant.atZone(zoneId).toOffsetDateTime();
}
@AfterTemplate
OffsetDateTime after(Instant instant, ZoneId zoneId) {
return OffsetDateTime.ofInstant(instant, zoneId);
}
}
/** Prefer {@link Instant#atOffset(ZoneOffset)} over more verbose alternatives. */
static final class InstantAtOffset {
@BeforeTemplate
OffsetDateTime before(Instant instant, ZoneOffset zoneOffset) {
@@ -77,6 +155,37 @@ final class TimeRules {
}
}
/** Prefer {@link OffsetTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
static final class OffsetTimeOfInstant {
@BeforeTemplate
OffsetTime before(Instant instant, ZoneId zoneId) {
return OffsetDateTime.ofInstant(instant, zoneId).toOffsetTime();
}
@BeforeTemplate
OffsetTime before(Instant instant, ZoneOffset zoneId) {
return instant.atOffset(zoneId).toOffsetTime();
}
@AfterTemplate
OffsetTime after(Instant instant, ZoneId zoneId) {
return OffsetTime.ofInstant(instant, zoneId);
}
}
/** Prefer {@link Instant#atZone(ZoneId)} over more verbose alternatives. */
static final class InstantAtZone {
@BeforeTemplate
ZonedDateTime before(Instant instant, ZoneId zoneId) {
return ZonedDateTime.ofInstant(instant, zoneId);
}
@AfterTemplate
ZonedDateTime after(Instant instant, ZoneId zoneId) {
return instant.atZone(zoneId);
}
}
/** Use {@link Clock#systemUTC()} when possible. */
static final class UtcClock {
@BeforeTemplate

View File

@@ -94,13 +94,11 @@ final class AutowiredConstructorTest {
"",
"interface Container {",
" class A {",
"",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
"",
" B(String x) {}",
" }",
"}")

View File

@@ -65,6 +65,23 @@ final class CollectorMutabilityTest {
.doTest();
}
@Test
void identificationWithoutGuavaOnClasspath() {
compilationTestHelper
.withClasspath()
.addSourceLines(
"A.java",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"",
"class A {",
" void m() {",
" Stream.empty().collect(Collectors.toList());",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper

View File

@@ -76,8 +76,18 @@ final class EmptyMethodTest {
" void instanceMethod() {}",
"",
" static void staticMethod() {}",
"",
" static void staticMethodWithComment() {",
" /* Foo. */",
" }",
"}")
.addOutputLines(
"A.java",
"final class A {",
" static void staticMethodWithComment() {",
" /* Foo. */",
" }",
"}")
.addOutputLines("A.java", "final class A {}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -4,6 +4,7 @@ import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
@@ -33,6 +34,14 @@ final class FluxFlatMapUsageTest {
" Flux.just(1).flatMapSequential(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).<String>flatMap(i -> Flux.just(String.valueOf(i)));",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
"",
" Mono.just(1).flatMap(Mono::just);",
" Flux.just(1).concatMap(Flux::just);",
@@ -71,9 +80,13 @@ final class FluxFlatMapUsageTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" private static final int MAX_CONCURRENCY = 8;",
"",
" void m() {",
" Flux.just(1).flatMap(Flux::just);",
" Flux.just(1).flatMapSequential(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" }",
"}")
.addOutputLines(
@@ -81,12 +94,16 @@ final class FluxFlatMapUsageTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" private static final int MAX_CONCURRENCY = 8;",
"",
" void m() {",
" Flux.just(1).concatMap(Flux::just);",
" Flux.just(1).concatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
" }",
"}")
.doTest();
.doTest(TestMode.TEXT_MATCH);
}
@Test
@@ -103,6 +120,8 @@ final class FluxFlatMapUsageTest {
" void m() {",
" Flux.just(1).flatMap(Flux::just);",
" Flux.just(1).flatMapSequential(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" }",
"}")
.addOutputLines(
@@ -115,8 +134,10 @@ final class FluxFlatMapUsageTest {
" void m() {",
" Flux.just(1).flatMap(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
" }",
"}")
.doTest();
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,54 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IsInstanceLambdaUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.util.stream.Stream;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic contains:",
" Stream.of(1).filter(i -> i instanceof Integer);",
" Stream.of(2).filter(Integer.class::isInstance);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import java.util.stream.Stream;",
"",
"class A {",
" void m() {",
" Stream.of(1).filter(i -> i instanceof Integer);",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.stream.Stream;",
"",
"class A {",
" void m() {",
" Stream.of(1).filter(Integer.class::isInstance);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,134 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitClassModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"Container.java",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.springframework.boot.test.context.TestConfiguration;",
"import org.springframework.context.annotation.Configuration;",
"",
"class Container {",
" final class FinalAndPackagePrivate {",
" @Test",
" void foo() {}",
" }",
"",
" final class FinalAndPackagePrivateWithCustomTestMethod {",
" @ParameterizedTest",
" void foo() {}",
" }",
"",
" public abstract class Abstract {",
" @Test",
" void foo() {}",
" }",
"",
" @Configuration",
" class WithConfigurationAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" @TestConfiguration",
" class WithConfigurationMetaAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" private final class Private {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" protected final class Protected {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" public final class Public {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" class NonFinal {",
" @Test",
" void foo() {}",
" }",
"",
" // BUG: Diagnostic contains:",
" class NonFinalWithCustomTestMethod {",
" @ParameterizedTest",
" void foo() {}",
" }",
"",
" @Configuration",
" // BUG: Diagnostic contains:",
" public class PublicWithConfigurationAnnotation {",
" @Test",
" void foo() {}",
" }",
"",
" @TestConfiguration",
" // BUG: Diagnostic contains:",
" protected class ProtectedWithConfigurationMetaAnnotation {",
" @Test",
" void foo() {}",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"import org.springframework.context.annotation.Configuration;",
"",
"public class A {",
" @Test",
" void foo() {}",
"",
" @Configuration",
" private static class B {",
" @Test",
" void bar() {}",
" }",
"}")
.addOutputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"import org.springframework.context.annotation.Configuration;",
"",
"final class A {",
" @Test",
" void foo() {}",
"",
" @Configuration",
" static class B {",
" @Test",
" void bar() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -4,6 +4,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
import org.springframework.scheduling.annotation.Scheduled;
final class ScheduledTransactionTraceTest {
private final CompilationTestHelper compilationTestHelper =
@@ -43,6 +44,21 @@ final class ScheduledTransactionTraceTest {
.doTest();
}
@Test
void identificationWithoutNewRelicAgentApiOnClasspath() {
compilationTestHelper
.withClasspath(Scheduled.class)
.addSourceLines(
"A.java",
"import org.springframework.scheduling.annotation.Scheduled;",
"",
"class A {",
" @Scheduled(fixedDelay = 1)",
" void scheduledButNotTraced() {}",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper

View File

@@ -0,0 +1,104 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StringCaseLocaleUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StringCaseLocaleUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(StringCaseLocaleUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.util.Locale.ROOT;",
"",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.ROOT);",
" \"a\".toUpperCase(Locale.ROOT);",
" \"b\".toLowerCase(ROOT);",
" \"b\".toUpperCase(ROOT);",
" \"c\".toLowerCase(Locale.getDefault());",
" \"c\".toUpperCase(Locale.getDefault());",
" \"d\".toLowerCase(Locale.ENGLISH);",
" \"d\".toUpperCase(Locale.ENGLISH);",
" \"e\".toLowerCase(new Locale(\"foo\"));",
" \"e\".toUpperCase(new Locale(\"foo\"));",
"",
" // BUG: Diagnostic contains:",
" \"f\".toLowerCase();",
" // BUG: Diagnostic contains:",
" \"g\".toUpperCase();",
"",
" String h = \"h\";",
" // BUG: Diagnostic contains:",
" h.toLowerCase();",
" String i = \"i\";",
" // BUG: Diagnostic contains:",
" i.toUpperCase();",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase(/* Comment with parens: (). */ );",
" \"b\".toUpperCase();",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.ROOT /* Comment with parens: (). */);",
" \"b\".toUpperCase(Locale.ROOT);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase();",
" \"b\".toUpperCase(/* Comment with parens: (). */ );",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.getDefault());",
" \"b\".toUpperCase(Locale.getDefault() /* Comment with parens: (). */);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,17 +1,11 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class TimeZoneUsageTest {
private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass())
.expectErrorMessage(
"X",
containsPattern(
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone"));
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass());
@Test
void identification() {
@@ -26,7 +20,10 @@ final class TimeZoneUsageTest {
"import java.time.LocalDate;",
"import java.time.LocalDateTime;",
"import java.time.LocalTime;",
"import java.time.OffsetDateTime;",
"import java.time.OffsetTime;",
"import java.time.ZoneId;",
"import java.time.ZonedDateTime;",
"",
"class A {",
" void m() {",
@@ -36,48 +33,69 @@ final class TimeZoneUsageTest {
" Clock.offset(clock, Duration.ZERO);",
" Clock.tick(clock, Duration.ZERO);",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.systemUTC();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.systemDefaultZone();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.system(UTC);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.tickMillis(UTC);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.tickMinutes(UTC);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Clock.tickSeconds(UTC);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" clock.getZone();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" clock.withZone(UTC);",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" Instant.now();",
" // This is equivalent to `clock.instant()`, which is fine.",
" Instant.now(clock);",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDate.now();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDate.now(clock);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDate.now(UTC);",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDateTime.now();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDateTime.now(clock);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalDateTime.now(UTC);",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalTime.now();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalTime.now(clock);",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" LocalTime.now(UTC);",
"",
" // BUG: Diagnostic contains:",
" OffsetDateTime.now();",
" // BUG: Diagnostic contains:",
" OffsetDateTime.now(clock);",
" // BUG: Diagnostic contains:",
" OffsetDateTime.now(UTC);",
"",
" // BUG: Diagnostic contains:",
" OffsetTime.now();",
" // BUG: Diagnostic contains:",
" OffsetTime.now(clock);",
" // BUG: Diagnostic contains:",
" OffsetTime.now(UTC);",
"",
" // BUG: Diagnostic contains:",
" ZonedDateTime.now();",
" // BUG: Diagnostic contains:",
" ZonedDateTime.now(clock);",
" // BUG: Diagnostic contains:",
" ZonedDateTime.now(UTC);",
" }",
"",
" abstract class ForwardingClock extends Clock {",

View File

@@ -0,0 +1,110 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodTree;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
final class MoreASTHelpersTest {
@Test
void findMethods() {
CompilationTestHelper.newInstance(FindMethodsTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void foo() {}",
"",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=1, bar=2, baz=0}",
" void bar(int i) {}",
"",
" static class B {",
" // BUG: Diagnostic contains: {foo=0, bar=1, baz=1}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=0, bar=1, baz=1}",
" void baz() {}",
" }",
"}")
.doTest();
}
@Test
void methodExistsInEnclosingClass() {
CompilationTestHelper.newInstance(MethodExistsTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void foo() {}",
"",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=true, bar=true, baz=false}",
" void bar(int i) {}",
"",
" static class B {",
" // BUG: Diagnostic contains: {foo=false, bar=true, baz=true}",
" void bar() {}",
"",
" // BUG: Diagnostic contains: {foo=false, bar=true, baz=true}",
" void baz() {}",
" }",
"}")
.doTest();
}
private static String createDiagnosticsMessage(
BiFunction<String, VisitorState, Object> valueFunction, VisitorState state) {
return Maps.toMap(ImmutableSet.of("foo", "bar", "baz"), key -> valueFunction.apply(key, state))
.toString();
}
/**
* A {@link BugChecker} that delegates to {@link MoreASTHelpers#findMethods(CharSequence,
* VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class FindMethodsTestChecker extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
createDiagnosticsMessage(
(methodName, s) -> MoreASTHelpers.findMethods(methodName, s).size(), state))
.build();
}
}
/**
* A {@link BugChecker} that delegates to {@link
* MoreASTHelpers#methodExistsInEnclosingClass(CharSequence, VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class MethodExistsTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(createDiagnosticsMessage(MoreASTHelpers::methodExistsInEnclosingClass, state))
.build();
}
}
}

View File

@@ -0,0 +1,173 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.HAS_METHOD_SOURCE;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import java.util.Map;
import org.junit.jupiter.api.Test;
final class MoreJUnitMatchersTest {
@Test
void methodMatchers() {
CompilationTestHelper.newInstance(MethodMatchersTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import java.util.stream.Stream;",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" @BeforeAll",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" public void beforeAll() {}",
"",
" @BeforeEach",
" @Test",
" // BUG: Diagnostic contains: TEST_METHOD, SETUP_OR_TEARDOWN_METHOD",
" protected void beforeEachAndTest() {}",
"",
" @AfterEach",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" private void afterEach() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains: SETUP_OR_TEARDOWN_METHOD",
" private void afterAll() {}",
"",
" @Test",
" // BUG: Diagnostic contains: TEST_METHOD",
" void test() {}",
"",
" private static Stream<Arguments> booleanArgs() {",
" return Stream.of(arguments(false), arguments(true));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"booleanArgs\")",
" // BUG: Diagnostic contains: TEST_METHOD, HAS_METHOD_SOURCE",
" void parameterizedTest(boolean b) {}",
"",
" @RepeatedTest(2)",
" // BUG: Diagnostic contains: TEST_METHOD",
" private void repeatedTest() {}",
"",
" private void unannotatedMethod() {}",
"}")
.doTest();
}
@Test
void getMethodSourceFactoryNames() {
CompilationTestHelper.newInstance(MethodSourceFactoryNamesTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" @MethodSource",
" // BUG: Diagnostic contains: [matchingMethodSource]",
" void matchingMethodSource(boolean b) {}",
"",
" @MethodSource()",
" // BUG: Diagnostic contains: [matchingMethodSourceWithParens]",
" void matchingMethodSourceWithParens(boolean b) {}",
"",
" @MethodSource(\"\")",
" // BUG: Diagnostic contains: [matchingMethodSourceMadeExplicit]",
" void matchingMethodSourceMadeExplicit(boolean b) {}",
"",
" @MethodSource({\"\"})",
" // BUG: Diagnostic contains: [matchingMethodSourceMadeExplicitWithParens]",
" void matchingMethodSourceMadeExplicitWithParens(boolean b) {}",
"",
" @MethodSource({})",
" // BUG: Diagnostic contains: []",
" void noMethodSources(boolean b) {}",
"",
" @MethodSource(\"myValueFactory\")",
" // BUG: Diagnostic contains: [myValueFactory]",
" void singleCustomMethodSource(boolean b) {}",
"",
" @MethodSource({\"firstValueFactory\", \"secondValueFactory\"})",
" // BUG: Diagnostic contains: [firstValueFactory, secondValueFactory]",
" void twoCustomMethodSources(boolean b) {}",
"",
" @MethodSource({\"myValueFactory\", \"\"})",
" // BUG: Diagnostic contains: [myValueFactory, customAndMatchingMethodSources]",
" void customAndMatchingMethodSources(boolean b) {}",
"}")
.doTest();
}
/**
* A {@link BugChecker} that flags methods matched by {@link Matcher}s of {@link MethodTree}s
* exposed by {@link MoreJUnitMatchers}.
*/
@BugPattern(summary = "Interacts with `MoreJUnitMatchers` for testing purposes", severity = ERROR)
public static final class MethodMatchersTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableMap<String, Matcher<MethodTree>> METHOD_MATCHERS =
ImmutableMap.of(
"TEST_METHOD", TEST_METHOD,
"HAS_METHOD_SOURCE", HAS_METHOD_SOURCE,
"SETUP_OR_TEARDOWN_METHOD", SETUP_OR_TEARDOWN_METHOD);
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
ImmutableSet<String> matches =
METHOD_MATCHERS.entrySet().stream()
.filter(e -> e.getValue().matches(tree, state))
.map(Map.Entry::getKey)
.collect(toImmutableSet());
return matches.isEmpty()
? Description.NO_MATCH
: buildDescription(tree).setMessage(String.join(", ", matches)).build();
}
}
/**
* A {@link BugChecker} that flags methods with a JUnit {@code @MethodSource} annotation by
* enumerating the associated value factory method names.
*/
@BugPattern(summary = "Interacts with `MoreJUnitMatchers` for testing purposes", severity = ERROR)
public static final class MethodSourceFactoryNamesTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
AnnotationTree annotation =
Iterables.getOnlyElement(HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes());
return buildDescription(tree)
.setMessage(MoreJUnitMatchers.getMethodSourceFactoryNames(annotation, tree).toString())
.build();
}
}
}

View File

@@ -0,0 +1,62 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.AnnotationTree;
import org.junit.jupiter.api.Test;
final class MoreMatchersTest {
@Test
void hasMetaAnnotation() {
CompilationTestHelper.newInstance(TestMatcher.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.api.TestTemplate;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" void negative1() {}",
"",
" @Test",
" void negative2() {}",
"",
" @AfterAll",
" void negative3() {}",
"",
" @TestTemplate",
" void negative4() {}",
"",
" // BUG: Diagnostic contains:",
" @ParameterizedTest",
" void positive1() {}",
"",
" // BUG: Diagnostic contains:",
" @RepeatedTest(2)",
" void positive2() {}",
"}")
.doTest();
}
/** A {@link BugChecker} that delegates to {@link MoreMatchers#hasMetaAnnotation(String)} . */
@BugPattern(summary = "Interacts with `MoreMatchers` for testing purposes", severity = ERROR)
public static final class TestMatcher extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<AnnotationTree> DELEGATE =
MoreMatchers.hasMetaAnnotation("org.junit.jupiter.api.TestTemplate");
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return DELEGATE.matches(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
}
}

View File

@@ -0,0 +1,195 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.superOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.Signatures;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
final class MoreTypesTest {
@Test
void matcher() {
CompilationTestHelper.newInstance(SubtypeFlagger.class, getClass())
.addSourceLines(
"/A.java",
"import java.util.Collection;",
"import java.util.List;",
"import java.util.Map;",
"import java.util.Optional;",
"import java.util.Set;",
"",
"class A<S, T> {",
" void m() {",
" Object object = factory();",
" A a = factory();",
"",
" // BUG: Diagnostic contains: [Number, ? super Number, Integer, ? super Integer]",
" int integer = factory();",
"",
" // BUG: Diagnostic contains: [String]",
" String string = factory();",
"",
" // BUG: Diagnostic contains: [Optional]",
" Optional rawOptional = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<S> optionalOfS = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<T> optionalOfT = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>, Optional<Number>]",
" Optional<Number> optionalOfNumber = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<Integer> optionalOfInteger = factory();",
"",
" // BUG: Diagnostic contains: [Collection]",
" Collection rawCollection = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
" Collection<Number> collectionOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
" Collection<Integer> collectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
" Collection<Short> collectionOfShort = factory();",
"",
" // BUG: Diagnostic contains: [Collection, List]",
" List rawList = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>, List, List<?>,",
" // List<Number>, List<? super Number>, List<? extends Number>, List<? super Integer>]",
" List<Number> listOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>, List,",
" // List<?>, List<? extends Number>, List<Integer>, List<? super Integer>, List<? extends",
" // Integer>]",
" List<Integer> listOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>, List,",
" // List<?>, List<? extends Number>]",
" List<Short> listOfShort = factory();",
"",
" // BUG: Diagnostic contains: [Collection]",
" Set rawSet = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
" Set<Number> setOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
" Set<Integer> setOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
" Set<Short> setOfShort = factory();",
"",
" Map rawMap = factory();",
" Map<Number, Collection<Number>> mapFromNumberToCollectionOfNumber = factory();",
" Map<Number, Collection<Short>> mapFromNumberToCollectionOfShort = factory();",
" Map<Number, Collection<Integer>> mapFromNumberToCollectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, Collection<Number>> mapFromStringToCollectionOfNumber = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, Collection<Short>> mapFromStringToCollectionOfShort = factory();",
" Map<String, Collection<Integer>> mapFromStringToCollectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, List<Number>> mapFromStringToListOfNumber = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, List<Short>> mapFromStringToListOfShort = factory();",
" Map<String, List<Integer>> mapFromStringToListOfInteger = factory();",
" }",
"",
" private <T> T factory() {",
" return null;",
" }",
"}")
.doTest();
}
/**
* A {@link BugChecker} that flags method invocations that are a subtype of any type defined by
* {@link #getTestTypes()}.
*/
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
public static final class SubtypeFlagger extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Type treeType = ASTHelpers.getType(tree);
List<String> matches = new ArrayList<>();
for (Supplier<Type> type : getTestTypes()) {
Type testType = type.get(state);
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
matches.add(Signatures.prettyType(testType));
}
}
return matches.isEmpty()
? Description.NO_MATCH
: buildDescription(tree).setMessage(matches.toString()).build();
}
/**
* Returns the type suppliers under test.
*
* @implNote The return value of this method should not be assigned to a field, as that would
* prevent mutations introduced by Pitest from being killed.
*/
private static ImmutableSet<Supplier<Type>> getTestTypes() {
return ImmutableSet.of(
// Invalid types.
type("java.lang.Nonexistent"),
generic(type("java.util.Integer"), unbound()),
// Valid types.
type("java.lang.String"),
type("java.lang.Number"),
superOf(type("java.lang.Number")),
subOf(type("java.lang.Number")),
type("java.lang.Integer"),
superOf(type("java.lang.Integer")),
subOf(type("java.lang.Integer")),
type("java.util.Optional"),
raw(type("java.util.Optional")),
generic(type("java.util.Optional"), unbound()),
generic(type("java.util.Optional"), type("java.lang.Number")),
type("java.util.Collection"),
raw(type("java.util.Collection")),
generic(type("java.util.Collection"), unbound()),
generic(type("java.util.Collection"), type("java.lang.Number")),
generic(type("java.util.Collection"), superOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), type("java.lang.Integer")),
generic(type("java.util.Collection"), superOf(type("java.lang.Integer"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Integer"))),
type("java.util.List"),
raw(type("java.util.List")),
generic(type("java.util.List"), unbound()),
generic(type("java.util.List"), type("java.lang.Number")),
generic(type("java.util.List"), superOf(type("java.lang.Number"))),
generic(type("java.util.List"), subOf(type("java.lang.Number"))),
generic(type("java.util.List"), type("java.lang.Integer")),
generic(type("java.util.List"), superOf(type("java.lang.Integer"))),
generic(type("java.util.List"), subOf(type("java.lang.Integer"))),
generic(
type("java.util.Map"),
type("java.lang.String"),
subOf(generic(type("java.util.Collection"), superOf(type("java.lang.Short"))))));
}
}
}

View File

@@ -0,0 +1,199 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
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.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import javax.lang.model.element.Name;
import org.junit.jupiter.api.Test;
final class SourceCodeTest {
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass());
@Test
void deleteWithTrailingWhitespaceAnnotations() {
refactoringTestHelper
.addInputLines("AnnotationToBeDeleted.java", "@interface AnnotationToBeDeleted {}")
.expectUnchanged()
.addInputLines(
"AnotherAnnotationToBeDeleted.java", "@interface AnotherAnnotationToBeDeleted {}")
.expectUnchanged()
.addInputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" @AnnotationToBeDeleted",
" void m() {}",
" }",
"",
" class FirstAnnotation {",
" @AnnotationToBeDeleted",
" @Deprecated",
" void m() {}",
" }",
"",
" class MiddleAnnotation {",
" @Deprecated",
" @AnnotationToBeDeleted",
" @SuppressWarnings(\"foo\")",
" void m() {}",
" }",
"",
" class LastAnnotation {",
" @Deprecated",
" @AnnotationToBeDeleted",
" void m() {}",
" }",
"",
" class MultipleAnnotations {",
" @AnnotationToBeDeleted",
" @AnotherAnnotationToBeDeleted",
" @Deprecated",
" void m() {}",
" }",
"}")
.addOutputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" void m() {}",
" }",
"",
" class FirstAnnotation {",
" @Deprecated",
" void m() {}",
" }",
"",
" class MiddleAnnotation {",
" @Deprecated",
" @SuppressWarnings(\"foo\")",
" void m() {}",
" }",
"",
" class LastAnnotation {",
" @Deprecated",
" void m() {}",
" }",
"",
" class MultipleAnnotations {",
" @Deprecated",
" void m() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void deleteWithTrailingWhitespaceMethods() {
refactoringTestHelper
.addInputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {",
" void methodToBeDeleted() {}",
" }",
"",
" class FirstMethod {",
" void methodToBeDeleted() {}",
"",
" void finalMethod() {}",
" }",
"",
" class MiddleMethod {",
" void initialMethod() {}",
"",
" void methodToBeDeleted() {}",
"",
" void finalMethod() {}",
" }",
"",
" class LastMethod {",
" void initialMethod() {}",
"",
" void methodToBeDeleted() {}",
" }",
"",
" class MultipleMethods {",
" void method1ToBeDeleted() {}",
"",
" void method2ToBeDeleted() {}",
"",
" void middleMethod() {}",
"",
" void method3ToBeDeleted() {}",
"",
" void method4ToBeDeleted() {}",
" }",
"}")
.addOutputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {}",
"",
" class FirstMethod {",
" void finalMethod() {}",
" }",
"",
" class MiddleMethod {",
" void initialMethod() {}",
"",
" void finalMethod() {}",
" }",
"",
" class LastMethod {",
" void initialMethod() {}",
" }",
"",
" class MultipleMethods {",
" void middleMethod() {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
/**
* A {@link BugChecker} that uses {@link SourceCode#deleteWithTrailingWhitespace(Tree,
* VisitorState)} to suggest the deletion of annotations and methods with a name containing
* {@value DELETION_MARKER}.
*/
@BugPattern(severity = ERROR, summary = "Interacts with `SourceCode` for testing purposes")
public static final class TestChecker extends BugChecker
implements AnnotationTreeMatcher, MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String DELETION_MARKER = "ToBeDeleted";
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return match(
tree,
ASTHelpers.getAnnotationMirror(tree).getAnnotationType().asElement().getSimpleName(),
state);
}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return match(tree, tree.getName(), state);
}
private Description match(Tree tree, Name name, VisitorState state) {
return name.toString().contains(DELETION_MARKER)
? describeMatch(tree, SourceCode.deleteWithTrailingWhitespace(tree, state))
: Description.NO_MATCH;
}
}
}

View File

@@ -0,0 +1,115 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.ClassTree;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Flux;
final class ThirdPartyLibraryTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(TestChecker.class, getClass());
@Test
void isIntroductionAllowed() {
compilationTestHelper
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
"class A {}")
.doTest();
}
@Test
void isIntroductionAllowedWitnessClassesInSymtab() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableList;",
"import com.newrelic.api.agent.Agent;",
"import org.assertj.core.api.Assertions;",
"import reactor.core.publisher.Flux;",
"",
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
"class A {",
" void m(Class<?> clazz) {",
" m(Assertions.class);",
" m(ImmutableList.class);",
" m(Agent.class);",
" m(Flux.class);",
" }",
"}")
.doTest();
}
@Test
void isIntroductionAllowedWitnessClassesPartiallyOnClassPath() {
compilationTestHelper
.withClasspath(ImmutableList.class, Flux.class)
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: true, NEW_RELIC_AGENT_API: false, REACTOR: true",
"class A {}")
.doTest();
}
@Test
void isIntroductionAllowedWitnessClassesNotOnClassPath() {
compilationTestHelper
.withClasspath()
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: false, NEW_RELIC_AGENT_API: false, REACTOR:",
"// false",
"class A {}")
.doTest();
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void isIntroductionAllowedIgnoreClasspathCompat(boolean ignoreClassPath) {
compilationTestHelper
.setArgs("-XepOpt:ErrorProneSupport:IgnoreClasspathCompat=" + ignoreClassPath)
.withClasspath(ImmutableList.class, Flux.class)
.addSourceLines(
"A.java",
String.format(
"// BUG: Diagnostic contains: ASSERTJ: %s, GUAVA: true, NEW_RELIC_AGENT_API: %s, REACTOR: true",
ignoreClassPath, ignoreClassPath),
"class A {}")
.doTest();
}
/**
* Flags classes with a diagnostics message that indicates, for each {@link ThirdPartyLibrary}
* element, whether they can be used.
*/
@BugPattern(severity = ERROR, summary = "Interacts with `ThirdPartyLibrary` for testing purposes")
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
Arrays.stream(ThirdPartyLibrary.values())
.map(
lib ->
String.join(
": ", lib.name(), String.valueOf(lib.isIntroductionAllowed(state))))
.collect(joining(", ")))
.build();
}
}
}

View File

@@ -52,6 +52,7 @@ final class RefasterRulesTest {
JUnitRules.class,
LongStreamRules.class,
MapEntryRules.class,
MapRules.class,
MockitoRules.class,
MultimapRules.class,
NullRules.class,

View File

@@ -2,18 +2,143 @@ package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.assertj.core.api.AbstractMapAssert;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
AbstractMapAssert<?, ?, Integer, Integer>
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
ArrayList.class,
HashMap.class,
HashSet.class,
ImmutableBiMap.class,
ImmutableList.class,
ImmutableMultiset.class,
ImmutableSortedMap.class,
ImmutableSortedMultiset.class,
ImmutableSortedSet.class,
LinkedHashMap.class,
TreeMap.class,
TreeSet.class);
}
void testAbstractMapAssertIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).containsExactlyEntriesOf(ImmutableMap.of());
assertThat(ImmutableMap.of(2, 0)).containsExactlyEntriesOf(ImmutableBiMap.of());
assertThat(ImmutableMap.of(3, 0)).containsExactlyEntriesOf(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(4, 0)).containsExactlyEntriesOf(new HashMap<>());
assertThat(ImmutableMap.of(5, 0)).containsExactlyEntriesOf(new LinkedHashMap<>());
assertThat(ImmutableMap.of(6, 0)).containsExactlyEntriesOf(new TreeMap<>());
assertThat(ImmutableMap.of(7, 0)).hasSameSizeAs(ImmutableMap.of());
assertThat(ImmutableMap.of(8, 0)).hasSameSizeAs(ImmutableBiMap.of());
assertThat(ImmutableMap.of(9, 0)).hasSameSizeAs(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(10, 0)).hasSameSizeAs(new HashMap<>());
assertThat(ImmutableMap.of(11, 0)).hasSameSizeAs(new LinkedHashMap<>());
assertThat(ImmutableMap.of(12, 0)).hasSameSizeAs(new TreeMap<>());
assertThat(ImmutableMap.of(13, 0)).isEqualTo(ImmutableMap.of());
assertThat(ImmutableMap.of(14, 0)).isEqualTo(ImmutableBiMap.of());
assertThat(ImmutableMap.of(15, 0)).isEqualTo(ImmutableSortedMap.of());
assertThat(ImmutableMap.of(16, 0)).isEqualTo(new HashMap<>());
assertThat(ImmutableMap.of(17, 0)).isEqualTo(new LinkedHashMap<>());
assertThat(ImmutableMap.of(18, 0)).isEqualTo(new TreeMap<>());
assertThat(ImmutableMap.of(19, 0)).containsOnlyKeys(ImmutableList.of());
assertThat(ImmutableMap.of(20, 0)).containsOnlyKeys(new ArrayList<>());
assertThat(ImmutableMap.of(21, 0)).containsOnlyKeys(ImmutableSet.of());
assertThat(ImmutableMap.of(22, 0)).containsOnlyKeys(new HashSet<>());
assertThat(ImmutableMap.of(23, 0)).containsOnlyKeys(ImmutableSortedSet.of());
assertThat(ImmutableMap.of(24, 0)).containsOnlyKeys(new TreeSet<>());
assertThat(ImmutableMap.of(25, 0)).containsOnlyKeys(ImmutableMultiset.of());
assertThat(ImmutableMap.of(26, 0)).containsOnlyKeys(ImmutableSortedMultiset.of());
assertThat(ImmutableMap.of(27, 0)).containsExactly();
assertThat(ImmutableMap.of(28, 0)).containsOnly();
assertThat(ImmutableMap.of(29, 0)).containsOnlyKeys();
}
void testAssertThatMapIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).hasSize(0);
assertThat(ImmutableMap.of(2, 0).isEmpty()).isTrue();
assertThat(ImmutableMap.of(3, 0).size()).isEqualTo(0L);
assertThat(ImmutableMap.of(4, 0).size()).isNotPositive();
assertThat(ImmutableMap.of(5, 0).keySet()).isEmpty();
assertThat(ImmutableMap.of(6, 0).values()).isEmpty();
assertThat(ImmutableMap.of(7, 0).entrySet()).isEmpty();
}
ImmutableSet<MapAssert<Integer, Integer>> testAbstractMapAssertIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEqualTo(ImmutableMap.of()),
assertThat(ImmutableMap.of(2, 0)).isNotEqualTo(ImmutableBiMap.of()),
assertThat(ImmutableMap.of(3, 0)).isNotEqualTo(ImmutableSortedMap.of()),
assertThat(ImmutableMap.of(4, 0)).isNotEqualTo(new HashMap<>()),
assertThat(ImmutableMap.of(5, 0)).isNotEqualTo(new LinkedHashMap<>()),
assertThat(ImmutableMap.of(6, 0)).isNotEqualTo(new TreeMap<>()));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0).isEmpty()).isFalse(),
assertThat(ImmutableMap.of(2, 0).size()).isNotEqualTo(0),
assertThat(ImmutableMap.of(3, 0).size()).isPositive(),
assertThat(ImmutableMap.of(4, 0).keySet()).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0).values()).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0).entrySet()).isNotEmpty());
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
return assertThat(ImmutableMap.of(1, 2, 3, 4)).isEqualTo(ImmutableMap.of(1, 2, 3, 4));
}
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
return assertThat(ImmutableMap.of(1, 2))
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapHasSize() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 2).size()).isEqualTo(1),
assertThat(ImmutableMap.of(3, 4).keySet()).hasSize(1),
assertThat(ImmutableMap.of(5, 6).values()).hasSize(1),
assertThat(ImmutableMap.of(7, 8).entrySet()).hasSize(1));
}
MapAssert<Integer, Integer> testAbstractMapAssertHasSameSizeAs() {
return assertThat(ImmutableMap.of(1, 2)).hasSize(ImmutableMap.of(3, 4).size());
}
AbstractAssert<?, ?> testAssertThatMapContainsKey() {
return assertThat(ImmutableMap.of(1, 2).containsKey(3)).isTrue();
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainKey() {
return assertThat(ImmutableMap.of(1, 2).containsKey(3)).isFalse();
}
AbstractAssert<?, ?> testAssertThatMapContainsValue() {
return assertThat(ImmutableMap.of(1, 2).containsValue(3)).isTrue();
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainValue() {
return assertThat(ImmutableMap.of(1, 2).containsValue(3)).isFalse();
}
AbstractObjectAssert<?, ?> testAssertThatMapContainsEntry() {
return assertThat(ImmutableMap.of(1, 2).get(1)).isEqualTo(2);
}
}

View File

@@ -2,18 +2,143 @@ package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.assertj.core.api.AbstractMapAssert;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJMapRulesTest implements RefasterRuleCollectionTestCase {
AbstractMapAssert<?, ?, Integer, Integer>
testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
ArrayList.class,
HashMap.class,
HashSet.class,
ImmutableBiMap.class,
ImmutableList.class,
ImmutableMultiset.class,
ImmutableSortedMap.class,
ImmutableSortedMultiset.class,
ImmutableSortedSet.class,
LinkedHashMap.class,
TreeMap.class,
TreeSet.class);
}
void testAbstractMapAssertIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).isEmpty();
assertThat(ImmutableMap.of(2, 0)).isEmpty();
assertThat(ImmutableMap.of(3, 0)).isEmpty();
assertThat(ImmutableMap.of(4, 0)).isEmpty();
assertThat(ImmutableMap.of(5, 0)).isEmpty();
assertThat(ImmutableMap.of(6, 0)).isEmpty();
assertThat(ImmutableMap.of(7, 0)).isEmpty();
assertThat(ImmutableMap.of(8, 0)).isEmpty();
assertThat(ImmutableMap.of(9, 0)).isEmpty();
assertThat(ImmutableMap.of(10, 0)).isEmpty();
assertThat(ImmutableMap.of(11, 0)).isEmpty();
assertThat(ImmutableMap.of(12, 0)).isEmpty();
assertThat(ImmutableMap.of(13, 0)).isEmpty();
assertThat(ImmutableMap.of(14, 0)).isEmpty();
assertThat(ImmutableMap.of(15, 0)).isEmpty();
assertThat(ImmutableMap.of(16, 0)).isEmpty();
assertThat(ImmutableMap.of(17, 0)).isEmpty();
assertThat(ImmutableMap.of(18, 0)).isEmpty();
assertThat(ImmutableMap.of(19, 0)).isEmpty();
assertThat(ImmutableMap.of(20, 0)).isEmpty();
assertThat(ImmutableMap.of(21, 0)).isEmpty();
assertThat(ImmutableMap.of(22, 0)).isEmpty();
assertThat(ImmutableMap.of(23, 0)).isEmpty();
assertThat(ImmutableMap.of(24, 0)).isEmpty();
assertThat(ImmutableMap.of(25, 0)).isEmpty();
assertThat(ImmutableMap.of(26, 0)).isEmpty();
assertThat(ImmutableMap.of(27, 0)).isEmpty();
assertThat(ImmutableMap.of(28, 0)).isEmpty();
assertThat(ImmutableMap.of(29, 0)).isEmpty();
}
void testAssertThatMapIsEmpty() {
assertThat(ImmutableMap.of(1, 0)).isEmpty();
assertThat(ImmutableMap.of(2, 0)).isEmpty();
assertThat(ImmutableMap.of(3, 0)).isEmpty();
assertThat(ImmutableMap.of(4, 0)).isEmpty();
assertThat(ImmutableMap.of(5, 0)).isEmpty();
assertThat(ImmutableMap.of(6, 0)).isEmpty();
assertThat(ImmutableMap.of(7, 0)).isEmpty();
}
ImmutableSet<MapAssert<Integer, Integer>> testAbstractMapAssertIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(2, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(3, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(4, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0)).isNotEmpty());
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapIsNotEmpty() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(2, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(3, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(4, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(5, 0)).isNotEmpty(),
assertThat(ImmutableMap.of(6, 0)).isNotEmpty());
}
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyInAnyOrderEntriesOf() {
return assertThat(ImmutableMap.of(1, 2, 3, 4))
.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(1, 2, 3, 4));
}
AbstractMapAssert<?, ?, Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
MapAssert<Integer, Integer> testAbstractMapAssertContainsExactlyEntriesOf() {
return assertThat(ImmutableMap.of(1, 2)).containsExactlyEntriesOf(ImmutableMap.of(1, 2));
}
ImmutableSet<AbstractAssert<?, ?>> testAssertThatMapHasSize() {
return ImmutableSet.of(
assertThat(ImmutableMap.of(1, 2)).hasSize(1),
assertThat(ImmutableMap.of(3, 4)).hasSize(1),
assertThat(ImmutableMap.of(5, 6)).hasSize(1),
assertThat(ImmutableMap.of(7, 8)).hasSize(1));
}
MapAssert<Integer, Integer> testAbstractMapAssertHasSameSizeAs() {
return assertThat(ImmutableMap.of(1, 2)).hasSameSizeAs(ImmutableMap.of(3, 4));
}
AbstractAssert<?, ?> testAssertThatMapContainsKey() {
return assertThat(ImmutableMap.of(1, 2)).containsKey(3);
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainKey() {
return assertThat(ImmutableMap.of(1, 2)).doesNotContainKey(3);
}
AbstractAssert<?, ?> testAssertThatMapContainsValue() {
return assertThat(ImmutableMap.of(1, 2)).containsValue(3);
}
AbstractAssert<?, ?> testAssertThatMapDoesNotContainValue() {
return assertThat(ImmutableMap.of(1, 2)).doesNotContainValue(3);
}
AbstractObjectAssert<?, ?> testAssertThatMapContainsEntry() {
return assertThat(ImmutableMap.of(1, 2).get(1)).isEqualTo(2);
}
}

View File

@@ -6,16 +6,12 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
@@ -23,7 +19,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -43,14 +38,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
}
}
Map<RoundingMode, String> testCreateEnumMap() {
return new HashMap<>();
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
}
@@ -95,14 +82,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
return !ImmutableList.of().iterator().hasNext();
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getKey);
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getValue);
}
ImmutableSet<Stream<String>> testSplitToStream() {
return ImmutableSet.of(
Streams.stream(Splitter.on(':').split("foo")),

View File

@@ -8,18 +8,13 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
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 java.math.RoundingMode;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
@@ -27,7 +22,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -45,14 +39,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
checkIndex(1, 2);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new EnumMap<>(RoundingMode.class);
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").get("bar");
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
}
@@ -95,14 +81,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
return Iterables.isEmpty(ImmutableList.of());
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).keySet().stream();
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).values().stream();
}
ImmutableSet<Stream<String>> testSplitToStream() {
return ImmutableSet.of(
Splitter.on(':').splitToStream("foo"),

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.valueOf(10), BigDecimal.valueOf(10L), new BigDecimal("10"));
}
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(new BigDecimal(0), new BigDecimal(0L));
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(new BigDecimal(2), new BigDecimal(2L), new BigDecimal(2.0));
}
}

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.TEN, BigDecimal.TEN, BigDecimal.TEN);
}
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(BigDecimal.valueOf(0), BigDecimal.valueOf(0L));
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(BigDecimal.valueOf(2), BigDecimal.valueOf(2L), BigDecimal.valueOf(2.0));
}
}

View File

@@ -186,4 +186,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
? Optional.ofNullable(new LinkedList<String>().remove())
: Optional.empty());
}
void testCollectionForEach() {
ImmutableSet.of(1).stream().forEach(String::valueOf);
}
}

View File

@@ -136,4 +136,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
Optional.ofNullable(new LinkedList<String>().poll()),
Optional.ofNullable(new LinkedList<String>().poll()));
}
void testCollectionForEach() {
ImmutableSet.of(1).forEach(String::valueOf);
}
}

View File

@@ -4,30 +4,43 @@ 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 java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(
String::compareTo,
Comparator.comparing(identity()),
Comparator.comparing(s -> s),
Collections.<String>reverseOrder(reverseOrder()),
Comparator.<String>reverseOrder().reversed());
}
Comparator<String> testReverseOrder() {
return Comparator.<String>naturalOrder().reversed();
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Collections.reverseOrder(),
Collections.reverseOrder(String::compareTo),
Collections.<String>reverseOrder(naturalOrder()),
Comparator.<String>naturalOrder().reversed());
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -77,8 +90,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(s -> s));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of(
Comparator.<String>naturalOrder().compare("foo", "bar"),
Comparator.<String>reverseOrder().compare("baz", "qux"));
}
int testMinOfVarargs() {
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") <= 0 ? "a" : "b",
"a".compareTo("b") > 0 ? "b" : "a",
"a".compareTo("b") < 0 ? "a" : "b",
"a".compareTo("b") >= 0 ? "b" : "a",
Comparators.min("a", "b", naturalOrder()),
Comparators.max("a", "b", reverseOrder()),
Collections.min(Arrays.asList("a", "b")),
Collections.min(ImmutableList.of("a", "b")),
Collections.min(ImmutableSet.of("a", "b")));
@@ -86,13 +115,27 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Collections.min(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.min(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.min(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "b" : "a",
Collections.min(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.min(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
int testMaxOfVarargs() {
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") >= 0 ? "a" : "b",
"a".compareTo("b") < 0 ? "b" : "a",
"a".compareTo("b") > 0 ? "a" : "b",
"a".compareTo("b") <= 0 ? "b" : "a",
Comparators.max("a", "b", naturalOrder()),
Comparators.min("a", "b", reverseOrder()),
Collections.max(Arrays.asList("a", "b")),
Collections.max(ImmutableList.of("a", "b")),
Collections.max(ImmutableSet.of("a", "b")));
@@ -100,9 +143,13 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Collections.max(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.max(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.max(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "b" : "a",
Collections.max(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.max(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.max(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -11,21 +11,32 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(naturalOrder(), naturalOrder(), naturalOrder());
return ImmutableSet.of(
naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder());
}
Comparator<String> testReverseOrder() {
return reverseOrder();
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Comparator.reverseOrder(),
Comparator.reverseOrder(),
Comparator.reverseOrder(),
Comparator.reverseOrder());
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -68,28 +79,64 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(naturalOrder()));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
}
int testMinOfVarargs() {
return Collections.min(Arrays.asList(1, 2), naturalOrder());
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.min("a", "b"), Comparators.min("a", "b"), Comparators.min("a", "b"));
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("b", "a"),
Comparators.min("b", "a"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"));
}
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.min(new Object(), new Object(), (a, b) -> -1),
Comparators.min(new Object(), new Object(), (a, b) -> 0),
Comparators.min(new Object(), new Object(), (a, b) -> 1));
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", (a, b) -> -1),
Comparators.min("a", "b", (a, b) -> 0),
Comparators.min("a", "b", (a, b) -> 1));
}
int testMaxOfVarargs() {
return Collections.max(Arrays.asList(1, 2), naturalOrder());
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.max("a", "b"), Comparators.max("a", "b"), Comparators.max("a", "b"));
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("b", "a"),
Comparators.max("b", "a"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"));
}
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.max(new Object(), new Object(), (a, b) -> -1),
Comparators.max(new Object(), new Object(), (a, b) -> 0),
Comparators.max(new Object(), new Object(), (a, b) -> 1));
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", (a, b) -> -1),
Comparators.max("a", "b", (a, b) -> 0),
Comparators.max("a", "b", (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -0,0 +1,54 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new HashMap<>();
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).keySet().isEmpty(),
ImmutableMap.of("bar", 2).values().isEmpty(),
ImmutableMap.of("baz", 3).entrySet().isEmpty());
}
ImmutableSet<Integer> testMapSize() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).keySet().size(),
ImmutableMap.of("bar", 2).values().size(),
ImmutableMap.of("baz", 3).entrySet().size());
}
boolean testMapContainsKey() {
return ImmutableMap.of("foo", 1).keySet().contains("bar");
}
boolean testMapContainsValue() {
return ImmutableMap.of("foo", 1).values().contains(2);
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getKey);
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).entrySet().stream().map(Map.Entry::getValue);
}
}

View File

@@ -0,0 +1,55 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new EnumMap<>(RoundingMode.class);
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").get("bar");
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).isEmpty(),
ImmutableMap.of("bar", 2).isEmpty(),
ImmutableMap.of("baz", 3).isEmpty());
}
ImmutableSet<Integer> testMapSize() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).size(),
ImmutableMap.of("bar", 2).size(),
ImmutableMap.of("baz", 3).size());
}
boolean testMapContainsKey() {
return ImmutableMap.of("foo", 1).containsKey("bar");
}
boolean testMapContainsValue() {
return ImmutableMap.of("foo", 1).containsValue(2);
}
Stream<String> testMapKeyStream() {
return ImmutableMap.of("foo", 1).keySet().stream();
}
Stream<Integer> testMapValueStream() {
return ImmutableMap.of("foo", 1).values().stream();
}
}

View File

@@ -109,4 +109,14 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
Optional.of("baz").stream().min(String::compareTo),
Optional.of("qux").stream().max(String::compareTo));
}
ImmutableSet<Optional<String>> testOptionalFilter() {
return ImmutableSet.of(
Optional.of("foo").stream().filter(String::isEmpty).findFirst(),
Optional.of("bar").stream().filter(String::isEmpty).findAny());
}
Optional<String> testOptionalMap() {
return Optional.of(1).stream().map(String::valueOf).findAny();
}
}

View File

@@ -103,4 +103,13 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Optional.of("foo"), Optional.of("bar"), Optional.of("baz"), Optional.of("qux"));
}
ImmutableSet<Optional<String>> testOptionalFilter() {
return ImmutableSet.of(
Optional.of("foo").filter(String::isEmpty), Optional.of("bar").filter(String::isEmpty));
}
Optional<String> testOptionalMap() {
return Optional.of(1).map(String::valueOf);
}
}

View File

@@ -1,5 +1,6 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableList;
@@ -15,6 +16,7 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.test.publisher.PublisherProbe;
import reactor.util.context.Context;
import reactor.util.function.Tuple2;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
@@ -32,12 +34,52 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromCallable(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
Mono<Integer> testMonoJust() {
return Mono.justOrEmpty(Optional.of(1));
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(Optional.ofNullable(1));
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.fromCallable(() -> Optional.of(1).orElse(null)),
Mono.fromSupplier(() -> Optional.of(2).orElse(null)));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::justOrEmpty);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Optional.of(1).map(Mono::just).orElse(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.just("foo").zipWith(Mono.just(1));
}
Mono<String> testMonoZipWithCombinator() {
return Mono.just("foo").zipWith(Mono.just(1), String::repeat);
}
Flux<Tuple2<String, Integer>> testFluxZip() {
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2));
}
Flux<String> testFluxZipWithCombinator() {
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2), String::repeat);
}
Flux<String> testFluxZipWithIterable() {
return Flux.just("foo", "bar").zipWithIterable(ImmutableSet.of(1, 2), String::repeat);
}
Mono<Void> testMonoDeferredError() {
return Mono.defer(() -> Mono.error(new IllegalStateException()));
}
@@ -58,6 +100,20 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.empty().then(Mono.just("foo"));
}
Flux<Integer> testFluxTake() {
return Flux.just(1, 2, 3).take(1);
}
Mono<String> testMonoDefaultIfEmpty() {
return Mono.just("foo").switchIfEmpty(Mono.just("bar"));
}
ImmutableSet<Flux<String>> testFluxDefaultIfEmpty() {
return ImmutableSet.of(
Flux.just("foo").switchIfEmpty(Mono.just("bar")),
Flux.just("baz").switchIfEmpty(Flux.just("qux")));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1).switchIfEmpty(Mono.empty());
}
@@ -69,12 +125,16 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
return ImmutableSet.of(
Flux.just(1).flatMap(Mono::just, 1), Flux.just(2).flatMapSequential(Mono::just, 1));
Flux.just(1).flatMap(Mono::just, 1),
Flux.just(2).flatMapSequential(Mono::just, 1),
Flux.just(3).map(Mono::just).concatMap(identity()));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
return ImmutableSet.of(
Flux.just(1).flatMap(Mono::just, 1, 3), Flux.just(2).flatMapSequential(Mono::just, 1, 4));
Flux.just(1).flatMap(Mono::just, 1, 3),
Flux.just(2).flatMapSequential(Mono::just, 1, 4),
Flux.just(3).map(Mono::just).concatMap(identity(), 5));
}
Flux<Integer> testFluxConcatMapIterable() {
@@ -86,17 +146,82 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
Flux<String> testMonoFlatMapToFlux() {
return Mono.just("foo").flatMapMany(s -> Mono.just(s + s));
return Mono.just("foo").flatMapMany(s -> Mono.fromSupplier(() -> s + s));
}
Flux<String> testMonoFlux() {
return Flux.concat(Mono.just("foo"));
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
ImmutableSet<Mono<String>> testMonoMap() {
return ImmutableSet.of(
Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty()),
Mono.just("bar").map(Optional::of).switchIfEmpty(Mono.just(Optional.empty())));
Mono.just("foo").flatMap(s -> Mono.just(s)),
Mono.just("bar").flatMap(s -> Mono.just(s.substring(1))));
}
ImmutableSet<Flux<Integer>> testFluxMap() {
return ImmutableSet.of(
Flux.just(1).concatMap(n -> Mono.just(n)),
Flux.just(1).concatMap(n -> Flux.just(n * 2)),
Flux.just(1).concatMap(n -> Mono.just(n), 3),
Flux.just(1).concatMap(n -> Flux.just(n * 2), 3),
Flux.just(1).concatMapDelayError(n -> Mono.just(n)),
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2)),
Flux.just(1).concatMapDelayError(n -> Mono.just(n), 3),
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2), 3),
Flux.just(1).flatMap(n -> Mono.just(n), 3),
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3),
Flux.just(1).flatMap(n -> Mono.just(n), 3, 4),
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3, 4),
Flux.just(1).flatMapDelayError(n -> Mono.just(n), 3, 4),
Flux.just(1).flatMapDelayError(n -> Flux.just(n * 2), 3, 4),
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3),
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3),
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3, 4),
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3, 4),
Flux.just(1).flatMapSequentialDelayError(n -> Mono.just(n), 3, 4),
Flux.just(1).flatMapSequentialDelayError(n -> Flux.just(n * 2), 3, 4),
Flux.just(1).switchMap(n -> Mono.just(n)),
Flux.just(1).switchMap(n -> Flux.just(n * 2)));
}
ImmutableSet<Mono<String>> testMonoMapNotNull() {
return ImmutableSet.of(
Mono.just("foo").flatMap(s -> Mono.justOrEmpty(s)),
Mono.just("bar").flatMap(s -> Mono.fromSupplier(() -> s.substring(1))));
}
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
return ImmutableSet.of(
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n)),
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2)),
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n), 3),
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n)),
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2)),
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n), 3),
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3),
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3),
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3, 4),
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
Flux.just(1).flatMapDelayError(n -> Mono.justOrEmpty(n), 3, 4),
Flux.just(1).flatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3),
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3),
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3, 4),
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
Flux.just(1).flatMapSequentialDelayError(n -> Mono.justOrEmpty(n), 3, 4),
Flux.just(1).flatMapSequentialDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
Flux.just(1).switchMap(n -> Mono.justOrEmpty(n)),
Flux.just(1).switchMap(n -> Mono.fromSupplier(() -> n * 2)));
}
ImmutableSet<Flux<String>> testMonoFlux() {
return ImmutableSet.of(
Mono.just("foo").flatMapMany(Mono::just),
Mono.just("bar").flatMapMany(Flux::just),
Flux.concat(Mono.just("baz")));
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty());
}
Mono<Number> testMonoCast() {
@@ -107,6 +232,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).map(Number.class::cast);
}
Mono<String> testMonoFlatMap() {
return Mono.just("foo").map(Mono::just).flatMap(identity());
}
Flux<String> testMonoFlatMapMany() {
return Mono.just("foo").map(Mono::just).flatMapMany(identity());
}
ImmutableSet<Flux<String>> testConcatMapIterableIdentity() {
return ImmutableSet.of(
Flux.just(ImmutableList.of("foo")).concatMap(list -> Flux.fromIterable(list)),
@@ -119,6 +252,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2));
}
Mono<Integer> testMonoDoOnError() {
return Mono.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
}
Flux<Integer> testFluxDoOnError() {
return Flux.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
}
Mono<Integer> testMonoOnErrorComplete() {
return Mono.just(1).onErrorResume(e -> Mono.empty());
}
@@ -129,6 +270,63 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(2).onErrorResume(e -> Flux.empty()));
}
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
return ImmutableSet.of(
Mono.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
Mono.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()));
}
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
return ImmutableSet.of(
Flux.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
Flux.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()),
Flux.just(3).onErrorResume(AssertionError.class, e -> Flux.empty()));
}
Mono<Integer> testMonoOnErrorCompletePredicate() {
return Mono.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty());
}
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
return ImmutableSet.of(
Flux.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty()),
Flux.just(2).onErrorResume(e -> e.getCause() != null, e -> Flux.empty()));
}
Mono<Integer> testMonoOnErrorContinue() {
return Mono.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
}
Flux<Integer> testFluxOnErrorContinue() {
return Flux.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
}
Mono<Integer> testMonoOnErrorMap() {
return Mono.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
}
Flux<Integer> testFluxOnErrorMap() {
return Flux.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
}
Mono<Integer> testMonoOnErrorResume() {
return Mono.just(1)
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Mono.just(2));
}
Flux<Integer> testFluxOnErrorResume() {
return Flux.just(1)
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Flux.just(2));
}
Mono<Integer> testMonoOnErrorReturn() {
return Mono.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
}
Flux<Integer> testFluxOnErrorReturn() {
return Flux.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
}
ImmutableSet<Context> testContextEmpty() {
return ImmutableSet.of(Context.of(new HashMap<>()), Context.of(ImmutableMap.of()));
}

View File

@@ -3,6 +3,7 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.MoreCollectors.toOptional;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -14,9 +15,11 @@ import java.util.concurrent.Callable;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.function.TupleUtils;
import reactor.test.StepVerifier;
import reactor.test.publisher.PublisherProbe;
import reactor.util.context.Context;
import reactor.util.function.Tuple2;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
@@ -34,12 +37,55 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromSupplier(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.empty(), Mono.empty());
}
Mono<Integer> testMonoJust() {
return Mono.just(1);
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(1);
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.defer(() -> Mono.justOrEmpty(Optional.of(1))),
Mono.defer(() -> Mono.justOrEmpty(Optional.of(2))));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::just);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Mono.justOrEmpty(Optional.of(1)).switchIfEmpty(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.zip(Mono.just("foo"), Mono.just(1));
}
Mono<String> testMonoZipWithCombinator() {
return Mono.zip(Mono.just("foo"), Mono.just(1)).map(TupleUtils.function(String::repeat));
}
Flux<Tuple2<String, Integer>> testFluxZip() {
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2));
}
Flux<String> testFluxZipWithCombinator() {
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2))
.map(TupleUtils.function(String::repeat));
}
Flux<String> testFluxZipWithIterable() {
return Flux.just("foo", "bar")
.zipWithIterable(ImmutableSet.of(1, 2))
.map(function(String::repeat));
}
Mono<Void> testMonoDeferredError() {
return Mono.error(() -> new IllegalStateException());
}
@@ -60,6 +106,19 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.empty().thenReturn("foo");
}
Flux<Integer> testFluxTake() {
return Flux.just(1, 2, 3).take(1, true);
}
Mono<String> testMonoDefaultIfEmpty() {
return Mono.just("foo").defaultIfEmpty("bar");
}
ImmutableSet<Flux<String>> testFluxDefaultIfEmpty() {
return ImmutableSet.of(
Flux.just("foo").defaultIfEmpty("bar"), Flux.just("baz").defaultIfEmpty("qux"));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1);
}
@@ -69,12 +128,17 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
return ImmutableSet.of(Flux.just(1).concatMap(Mono::just), Flux.just(2).concatMap(Mono::just));
return ImmutableSet.of(
Flux.just(1).concatMap(Mono::just),
Flux.just(2).concatMap(Mono::just),
Flux.just(3).concatMap(Mono::just));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
return ImmutableSet.of(
Flux.just(1).concatMap(Mono::just, 3), Flux.just(2).concatMap(Mono::just, 4));
Flux.just(1).concatMap(Mono::just, 3),
Flux.just(2).concatMap(Mono::just, 4),
Flux.just(3).concatMap(Mono::just, 5));
}
Flux<Integer> testFluxConcatMapIterable() {
@@ -86,17 +150,77 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
Flux<String> testMonoFlatMapToFlux() {
return Mono.just("foo").flatMap(s -> Mono.just(s + s)).flux();
return Mono.just("foo").flatMap(s -> Mono.fromSupplier(() -> s + s)).flux();
}
Flux<String> testMonoFlux() {
return Mono.just("foo").flux();
ImmutableSet<Mono<String>> testMonoMap() {
return ImmutableSet.of(Mono.just("foo").map(s -> s), Mono.just("bar").map(s -> s.substring(1)));
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
ImmutableSet<Flux<Integer>> testFluxMap() {
return ImmutableSet.of(
Mono.just("foo").flux().collect(toOptional()),
Mono.just("bar").flux().collect(toOptional()));
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2),
Flux.just(1).map(n -> n),
Flux.just(1).map(n -> n * 2));
}
ImmutableSet<Mono<String>> testMonoMapNotNull() {
return ImmutableSet.of(
Mono.just("foo").mapNotNull(s -> s), Mono.just("bar").mapNotNull(s -> s.substring(1)));
}
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
return ImmutableSet.of(
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2),
Flux.just(1).mapNotNull(n -> n),
Flux.just(1).mapNotNull(n -> n * 2));
}
ImmutableSet<Flux<String>> testMonoFlux() {
return ImmutableSet.of(
Mono.just("foo").flux(), Mono.just("bar").flux(), Mono.just("baz").flux());
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").flux().collect(toOptional());
}
Mono<Number> testMonoCast() {
@@ -107,6 +231,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).cast(Number.class);
}
Mono<String> testMonoFlatMap() {
return Mono.just("foo").flatMap(Mono::just);
}
Flux<String> testMonoFlatMapMany() {
return Mono.just("foo").flatMapMany(Mono::just);
}
ImmutableSet<Flux<String>> testConcatMapIterableIdentity() {
return ImmutableSet.of(
Flux.just(ImmutableList.of("foo")).concatMapIterable(identity()),
@@ -119,6 +251,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2));
}
Mono<Integer> testMonoDoOnError() {
return Mono.just(1).doOnError(IllegalArgumentException.class, e -> {});
}
Flux<Integer> testFluxDoOnError() {
return Flux.just(1).doOnError(IllegalArgumentException.class, e -> {});
}
Mono<Integer> testMonoOnErrorComplete() {
return Mono.just(1).onErrorComplete();
}
@@ -127,6 +267,61 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(Flux.just(1).onErrorComplete(), Flux.just(2).onErrorComplete());
}
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
return ImmutableSet.of(
Mono.just(1).onErrorComplete(IllegalArgumentException.class),
Mono.just(2).onErrorComplete(IllegalStateException.class));
}
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
return ImmutableSet.of(
Flux.just(1).onErrorComplete(IllegalArgumentException.class),
Flux.just(2).onErrorComplete(IllegalStateException.class),
Flux.just(3).onErrorComplete(AssertionError.class));
}
Mono<Integer> testMonoOnErrorCompletePredicate() {
return Mono.just(1).onErrorComplete(e -> e.getCause() == null);
}
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
return ImmutableSet.of(
Flux.just(1).onErrorComplete(e -> e.getCause() == null),
Flux.just(2).onErrorComplete(e -> e.getCause() != null));
}
Mono<Integer> testMonoOnErrorContinue() {
return Mono.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
}
Flux<Integer> testFluxOnErrorContinue() {
return Flux.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
}
Mono<Integer> testMonoOnErrorMap() {
return Mono.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
}
Flux<Integer> testFluxOnErrorMap() {
return Flux.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
}
Mono<Integer> testMonoOnErrorResume() {
return Mono.just(1).onErrorResume(IllegalArgumentException.class, e -> Mono.just(2));
}
Flux<Integer> testFluxOnErrorResume() {
return Flux.just(1).onErrorResume(IllegalArgumentException.class, e -> Flux.just(2));
}
Mono<Integer> testMonoOnErrorReturn() {
return Mono.just(1).onErrorReturn(IllegalArgumentException.class, 2);
}
Flux<Integer> testFluxOnErrorReturn() {
return Flux.just(1).onErrorReturn(IllegalArgumentException.class, 2);
}
ImmutableSet<Context> testContextEmpty() {
return ImmutableSet.of(Context.empty(), Context.empty());
}

View File

@@ -8,6 +8,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
ZoneId.from(ZoneOffset.UTC));
}
ImmutableSet<LocalDate> testLocalDateOfInstant() {
return ImmutableSet.of(
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDate(),
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDate(),
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDate(),
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalDate());
}
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
return ImmutableSet.of(
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDateTime(),
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDateTime(),
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDateTime());
}
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
return ImmutableSet.of(
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalTime(),
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalTime(),
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalTime(),
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalTime(),
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX).toLocalTime());
}
OffsetDateTime testOffsetDateTimeOfInstant() {
return Instant.EPOCH.atZone(ZoneOffset.UTC).toOffsetDateTime();
}
OffsetDateTime testInstantAtOffset() {
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
}
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
return ImmutableSet.of(
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")).toOffsetTime(),
Instant.EPOCH.atOffset(ZoneOffset.UTC).toOffsetTime());
}
ZonedDateTime testInstantAtZone() {
return ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
}
Clock testUtcClock() {
return Clock.system(ZoneOffset.UTC);
}

View File

@@ -8,6 +8,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
ZoneOffset.UTC);
}
ImmutableSet<LocalDate> testLocalDateOfInstant() {
return ImmutableSet.of(
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.MIN));
}
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
return ImmutableSet.of(
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")));
}
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
return ImmutableSet.of(
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN),
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX));
}
OffsetDateTime testOffsetDateTimeOfInstant() {
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
}
OffsetDateTime testInstantAtOffset() {
return Instant.EPOCH.atOffset(ZoneOffset.UTC);
}
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
return ImmutableSet.of(
OffsetTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC));
}
ZonedDateTime testInstantAtZone() {
return Instant.EPOCH.atZone(ZoneOffset.UTC);
}
Clock testUtcClock() {
return Clock.systemUTC();
}

121
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -146,7 +146,7 @@
one place. We use these to keep dependencies in sync. Version numbers
that need to be referenced only once should *not* be listed here. -->
<version.auto-service>1.0.1</version.auto-service>
<version.auto-value>1.10</version.auto-value>
<version.auto-value>1.10.1</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.16</version.error-prone-orig>
@@ -154,18 +154,10 @@
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.8.6</version.maven>
<version.mockito>4.8.1</version.mockito>
<version.mockito>4.9.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.10.2</version.nullaway>
<!-- XXX: Two other dependencies are potentially of interest:
`com.palantir.assertj-automation:assertj-refaster-rules` and
`com.palantir.baseline:baseline-refaster-rules` contain Refaster rules
that aren't currently applied. We should use
`RefasterRuleBuilderScanner` to convert those to `.refaster` files so
that we can pick them up. (But in case of `baseline-refaster-rules`
perhaps we can simply incorporate all of them.) -->
<version.palantir-assertj-automation>0.6.0</version.palantir-assertj-automation>
<version.palantir-baseline>4.145.0</version.palantir-baseline>
<version.nullaway>0.10.5</version.nullaway>
<version.pitest-git>1.0.3</version.pitest-git>
<version.surefire>2.22.2</version.surefire>
</properties>
@@ -219,7 +211,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.13.4.20221013</version>
<version>2.14.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -277,21 +269,7 @@
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<version>7.10.0</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.palantir.assertj-automation</groupId>
<artifactId>assertj-error-prone</artifactId>
<version>${version.palantir-assertj-automation}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.palantir.baseline</groupId>
<artifactId>baseline-error-prone</artifactId>
<version>${version.palantir-baseline}</version>
<version>7.11.1</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
@@ -303,7 +281,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.24</version>
<version>2022.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -315,12 +293,12 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.8</version>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.4</version>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -365,7 +343,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.26.0</version>
<version>3.28.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -399,19 +377,19 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.3</version>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.23</version>
<version>5.3.24</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.7.5</version>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -451,6 +429,16 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-git-maven-plugin</artifactId>
<version>${version.pitest-git}</version>
</plugin>
<plugin>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-github-maven-plugin</artifactId>
<version>${version.pitest-git}</version>
</plugin>
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
@@ -783,7 +771,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.3.4</version>
<version>10.5.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -844,20 +832,6 @@
<artifactId>nopen-checker</artifactId>
<version>${version.nopen-checker}</version>
</path>
<!-- XXX: Before enabling these plugins we'll
need to resolve some violations. Some of the
checks will need to be disabled.
<path>
<groupId>com.palantir.assertj-automation</groupId>
<artifactId>assertj-error-prone</artifactId>
<version>${version.palantir-assertj-automation}</version>
</path>
<path>
<groupId>com.palantir.baseline</groupId>
<artifactId>baseline-error-prone</artifactId>
<version>${version.palantir-baseline}</version>
</path>
-->
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
@@ -896,16 +870,16 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<version>3.4.0</version>
<configuration>
<!-- XXX: Drop `ignoreAllNonTestScoped` once
https://issues.apache.org/jira/browse/MNG-6058 is
resolved. (See
https://issues.apache.org/jira/browse/MDEP-791 for
context.) -->
<ignoreAllNonTestScoped>true</ignoreAllNonTestScoped>
<ignoreDirect>false</ignoreDirect>
<ignoreNonCompile>true</ignoreNonCompile>
<!-- XXX: Drop/review this configuration once
https://issues.apache.org/jira/browse/MDEP-791 is
resolved. -->
<ignoredNonTestScopedDependencies>
<ignoredNonTestScopedDependency>*</ignoredNonTestScopedDependency>
</ignoredNonTestScopedDependencies>
</configuration>
</plugin>
<plugin>
@@ -984,7 +958,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.0.1</version>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -1216,7 +1190,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tidy-maven-plugin</artifactId>
<version>1.1.0</version>
<version>1.2.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -1229,7 +1203,7 @@
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.4.0</version>
<version>2.5.0</version>
<configuration>
<exclusionPatterns>
<!-- The plugin suggests replacing usages of
@@ -1265,11 +1239,19 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.9.8</version>
<version>1.10.3</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
<excludedClass>*.AutoValue_*</excludedClass>
<!-- Refaster rule collections. -->
<excludedClass>*.refaster*.*Rules*</excludedClass>
</excludedClasses>
<failWhenNoMutations>false</failWhenNoMutations>
<mutators>
<mutator>EXTENDED</mutator>
<mutator>STRONGER</mutator>
</mutators>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
contention. -->
@@ -1278,6 +1260,21 @@
<timestampedReports>false</timestampedReports>
</configuration>
<dependencies>
<dependency>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-git-plugin</artifactId>
<version>${version.pitest-git}</version>
</dependency>
<dependency>
<groupId>com.groupcdg.arcmutate</groupId>
<artifactId>base</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.groupcdg.pitest</groupId>
<artifactId>pitest-accelerator-junit5</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-compiler</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-runner</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-support</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.1-SNAPSHOT</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-test-support</artifactId>