Compare commits

...

86 Commits

Author SHA1 Message Date
Stephan Schroevers
b19befcacb TEST: Doesn't work
Investigate why.
2022-12-04 18:45:29 +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
Stephan Schroevers
b2e15607c1 Migrate from JSR 305 to JSpecify (#181)
JSpecify's annotations have more well-defined semantics. Its `@Nullable`
annotation is also a type-use annotation recognized by Google Java
Format, so the formatter places it after any field or method modifiers.

See https://jspecify.dev
2022-10-25 10:18:22 +02:00
Stephan Schroevers
50e730fb40 Have LexicographicalAnnotationListing sort TYPE_USE annotations last (#182)
This ensures compatibility with Error Prone's `AnnotationPosition`
check.
2022-10-25 10:01:23 +02:00
Picnic-Bot
a844b9e532 Upgrade actions/configure-pages v2.1.1 -> v2.1.2 (#312)
See:
- https://github.com/actions/configure-pages/releases/tag/v2.1.2
- https://github.com/actions/configure-pages/compare/v2.1.1...v2.1.2
2022-10-25 09:24:43 +02:00
Stephan Schroevers
671ee1eedb Don't update project.build.outputTimestamp on mvn versions:set (#310)
As we rely on the value of `git.commit.time` instead.
2022-10-24 13:31:35 +02:00
Picnic-Bot
7fe61c226a Upgrade versions-maven-plugin 2.12.0 -> 2.13.0 (#309)
See:
- https://github.com/mojohaus/versions-maven-plugin/releases/tag/2.13.0
- https://github.com/mojohaus/versions-maven-plugin/compare/versions-maven-plugin-2.12.0...2.13.0
2022-10-24 13:21:55 +02:00
Picnic-Bot
8fcc91febf Upgrade Spring Boot 2.7.4 -> 2.7.5 (#307)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.5
- https://github.com/spring-projects/spring-boot/compare/v2.7.4...v2.7.5
2022-10-24 10:14:17 +02:00
Rick Ossendrijver
e00aba12c3 Make the build JDK 18+ compatible (#304) 2022-10-23 18:15:38 +02:00
Phil Werli
0118cc6c10 Introduce Reactor ContextEmpty Refaster rule (#306) 2022-10-23 17:30:01 +02:00
Picnic-Bot
91e009cab0 Upgrade actions/setup-java v3.5.1 -> v3.6.0 (#305)
See:
- https://github.com/actions/setup-java/releases/tag/v3.6.0
- https://github.com/actions/setup-java/compare/v3.5.1...v3.6.0
2022-10-20 09:13:00 +02:00
Phil Werli
68dca3204e Introduce Guava Preconditions Refaster rules (#292) 2022-10-20 08:55:49 +02:00
Picnic-Bot
e6ccb523fd Upgrade Mockito 4.8.0 -> 4.8.1 (#303)
See:
- https://github.com/mockito/mockito/releases/tag/v4.8.1
- https://github.com/mockito/mockito/compare/v4.8.0...v4.8.1
2022-10-20 08:46:03 +02:00
Picnic-Bot
d0533f7190 Upgrade actions/deploy-pages v1.2.1 -> v1.2.2 (#302)
See:
- https://github.com/actions/deploy-pages/releases/tag/v1.2.2
- https://github.com/actions/deploy-pages/compare/v1.2.1...v1.2.2
2022-10-20 08:12:58 +02:00
Picnic-Bot
759e7ea2ff Upgrade swagger-annotations 2.2.3 -> 2.2.4 (#299)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.4
- https://github.com/swagger-api/swagger-core/compare/v2.2.3...v2.2.4
2022-10-17 14:54:27 +02:00
Picnic-Bot
2578857a37 Upgrade errorprone-slf4j 0.1.15 -> 0.1.16 (#296)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.16
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.15...v0.1.16
2022-10-17 11:10:20 +02:00
Picnic-Bot
1d28512f09 Upgrade swagger-annotations 1.6.7 -> 1.6.8 (#300)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.8
- https://github.com/swagger-api/swagger-core/compare/v1.6.7...v1.6.8
2022-10-17 10:44:11 +02:00
Picnic-Bot
4efd79bfa6 Upgrade Project Reactor 2020.0.23 -> 2020.0.24 (#295)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.24
- https://github.com/reactor/reactor/compare/2020.0.23...2020.0.24
2022-10-17 10:25:01 +02:00
Picnic-Bot
d6b28733f6 Upgrade Jackson 2.13.4 -> 2.13.4.20221013 (#294)
See:
- https://github.com/FasterXML/jackson-databind/compare/jackson-databind-2.13.4...jackson-databind-2.13.4.2
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.4...jackson-bom-2.13.4.20221013
2022-10-15 17:01:00 +02:00
Picnic-Bot
b9884fa6d6 Upgrade Error Prone 2.15.0 -> 2.16 (#291)
See:
- https://github.com/google/error-prone/releases/tag/v2.16
- https://github.com/google/error-prone/compare/v2.15.0...v2.16
- https://github.com/PicnicSupermarket/error-prone/compare/v2.15.0-picnic-3...v2.16-picnic-1
2022-10-15 13:22:02 +02:00
Picnic-Bot
4fd8f57430 Upgrade ruby/setup-ruby v1.117.0 -> v1.118.0 (#298)
See:
- https://github.com/ruby/setup-ruby/releases/tag/v1.118.0
- https://github.com/ruby/setup-ruby/compare/v1.117.0...v1.118.0
2022-10-15 11:03:17 +02:00
Jelmer Borst
a2559bddda Fix and simplify documented example compiler output (#297) 2022-10-13 10:12:37 +02:00
Stephan Schroevers
7de9bede19 [maven-release-plugin] prepare for next development iteration 2022-10-13 09:20:46 +02:00
116 changed files with 4623 additions and 636 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.5.1
uses: actions/setup-java@v3.7.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.117.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.1
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.1
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.7.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.7.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]
@@ -119,16 +120,13 @@ code with Maven should yield two compiler warnings:
```sh
$ mvn clean install
...
[INFO] -------------------------------------------------------------
[WARNING] COMPILATION WARNING :
[INFO] -------------------------------------------------------------
[WARNING] Example.java:[9,34] [tech.picnic.errorprone.refasterrules.BigDecimalRules.BigDecimalZero]
[INFO] Example.java:[9,34] [Refaster Rule] BigDecimalRules.BigDecimalZero: Refactoring opportunity
(see https://error-prone.picnic.tech/refasterrules/BigDecimalRules#BigDecimalZero)
Did you mean 'return BigDecimal.ZERO;'?
...
[WARNING] Example.java:[13,35] [IdentityConversion] This method invocation appears redundant; remove it or suppress this warning and add a comment explaining its purpose
(see https://error-prone.picnic.tech/bugpatterns/IdentityConversion)
Did you mean 'return set;' or '@SuppressWarnings("IdentityConversion") public ImmutableSet<Integer> getSet() {'?
[INFO] 2 warnings
[INFO] -------------------------------------------------------------
...
```
@@ -168,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].
@@ -229,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.0</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -64,11 +64,6 @@
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
@@ -98,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>
@@ -173,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

@@ -36,6 +36,9 @@ public final class AmbiguousJsonCreator extends BugChecker implements Annotation
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
isType("com.fasterxml.jackson.annotation.JsonCreator");
/** Instantiates a new {@link AmbiguousJsonCreator} instance. */
public AmbiguousJsonCreator() {}
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
if (!IS_JSON_CREATOR_ANNOTATION.matches(tree, state)) {

View File

@@ -44,6 +44,9 @@ public final class AssertJIsNull extends BugChecker implements MethodInvocationT
argumentCount(1),
argument(0, nullLiteral()));
/** Instantiates a new {@link AssertJIsNull} instance. */
public AssertJIsNull() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {

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)
@@ -38,6 +38,9 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
/** Instantiates a new {@link AutowiredConstructor} instance. */
public AutowiredConstructor() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
List<MethodTree> constructors = ASTHelpers.getConstructors(tree);
@@ -59,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

@@ -46,6 +46,9 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
CanonicalAnnotationSyntax::dropRedundantCurlies);
/** Instantiates a new {@link CanonicalAnnotationSyntax} instance. */
public CanonicalAnnotationSyntax() {}
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return FIX_FACTORIES.stream()

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
@@ -45,9 +46,13 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
private static final Matcher<ExpressionTree> SET_COLLECTOR =
staticMethod().anyClass().named("toSet");
/** Instantiates a new {@link CollectorMutability} instance. */
public CollectorMutability() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!COLLECTOR_METHOD.matches(tree, state)) {
if (!ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)
|| !COLLECTOR_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}

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)
@@ -38,6 +38,9 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
AT_LEAST_ONE,
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
/** Instantiates a new {@link EmptyMethod} instance. */
public EmptyMethod() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (tree.getBody() == null
@@ -52,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

@@ -72,6 +72,9 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
/** Instantiates a new {@link ErrorProneTestHelperSourceFormat} instance. */
public ErrorProneTestHelperSourceFormat() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);

View File

@@ -46,6 +46,9 @@ public final class ExplicitEnumOrdering extends BugChecker implements MethodInvo
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
staticMethod().onClass(Ordering.class.getName()).named("explicit");
/** Instantiates a new {@link ExplicitEnumOrdering} instance. */
public ExplicitEnumOrdering() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!EXPLICIT_ORDERING.matches(tree, state)) {

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,19 @@ public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
private static final Supplier<Type> FLUX =
Suppliers.typeFromString("reactor.core.publisher.Flux");
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.onDescendantOf(FLUX)
.namedAnyOf("flatMap", "flatMapSequential")
.withParameters(Function.class.getName());
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
VisitorState.memoize(
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
/** Instantiates a new {@link FluxFlatMapUsage} instance. */
public FluxFlatMapUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
@@ -64,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

@@ -32,7 +32,7 @@ import com.sun.source.util.SimpleTreeVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
@@ -129,6 +129,9 @@ public final class FormatStringConcatenation extends BugChecker
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("debug", "error", "info", "trace", "warn");
/** Instantiates a new {@link FormatStringConcatenation} instance. */
public FormatStringConcatenation() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (hasNonConstantStringConcatenationArgument(tree, 0, state)) {
@@ -204,7 +207,7 @@ public final class FormatStringConcatenation extends BugChecker
}
private static class ReplacementArgumentsConstructor
extends SimpleTreeVisitor<Void, VisitorState> {
extends SimpleTreeVisitor<@Nullable Void, VisitorState> {
private final StringBuilder formatString = new StringBuilder();
private final List<Tree> formatArguments = new ArrayList<>();
private final String formatSpecifier;
@@ -213,9 +216,8 @@ public final class FormatStringConcatenation extends BugChecker
this.formatSpecifier = formatSpecifier;
}
@Nullable
@Override
public Void visitBinary(BinaryTree tree, VisitorState state) {
public @Nullable Void visitBinary(BinaryTree tree, VisitorState state) {
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
tree.getLeftOperand().accept(this, state);
tree.getRightOperand().accept(this, state);
@@ -226,15 +228,13 @@ public final class FormatStringConcatenation extends BugChecker
return null;
}
@Nullable
@Override
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
public @Nullable Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
return tree.getExpression().accept(this, state);
}
@Nullable
@Override
protected Void defaultAction(Tree tree, VisitorState state) {
protected @Nullable Void defaultAction(Tree tree, VisitorState state) {
appendExpression(tree);
return null;
}

View File

@@ -72,6 +72,9 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
/** Instantiates a new {@link IdentityConversion} instance. */
public IdentityConversion() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();

View File

@@ -67,6 +67,9 @@ public final class ImmutablesSortedSetComparator extends BugChecker implements M
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
/** Instantiates a new {@link ImmutablesSortedSetComparator} instance. */
public ImmutablesSortedSetComparator() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {

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,9 @@ 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() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
@@ -139,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));
}
@@ -155,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)
@@ -185,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

@@ -40,7 +40,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
@@ -82,7 +82,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
private final AnnotationAttributeMatcher matcher;
/** Instantiates the default {@link LexicographicalAnnotationAttributeListing}. */
/** Instantiates a default {@link LexicographicalAnnotationAttributeListing} instance. */
public LexicographicalAnnotationAttributeListing() {
this(ErrorProneFlags.empty());
}
@@ -184,17 +184,15 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
private static ImmutableList<ImmutableList<String>> getStructure(ExpressionTree array) {
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
new TreeScanner<Void, Void>() {
@Nullable
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
nodes.add(ImmutableList.of(node.getName().toString()));
return super.visitIdentifier(node, unused);
}
@Nullable
@Override
public Void visitLiteral(LiteralTree node, @Nullable Void unused) {
public @Nullable Void visitLiteral(LiteralTree node, @Nullable Void unused) {
Object value = ASTHelpers.constValue(node);
nodes.add(
value instanceof String
@@ -204,9 +202,8 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
return super.visitLiteral(node, unused);
}
@Nullable
@Override
public Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString()));
return super.visitPrimitiveType(node, unused);
}

View File

@@ -4,10 +4,13 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.DECLARATION;
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.TYPE;
import static java.util.Comparator.comparing;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
@@ -17,10 +20,14 @@ import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.TypeAnnotations.AnnotationType;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
@@ -39,6 +46,12 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
public final class LexicographicalAnnotationListing extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Comparator<@Nullable AnnotationType> BY_ANNOTATION_TYPE =
(a, b) ->
(a == null || a == DECLARATION) && b == TYPE ? -1 : a == TYPE && b == DECLARATION ? 1 : 0;
/** Instantiates a new {@link LexicographicalAnnotationListing} instance. */
public LexicographicalAnnotationListing() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
@@ -47,26 +60,29 @@ public final class LexicographicalAnnotationListing extends BugChecker
return Description.NO_MATCH;
}
ImmutableList<? extends AnnotationTree> sortedAnnotations = sort(originalOrdering, state);
ImmutableList<? extends AnnotationTree> sortedAnnotations =
sort(originalOrdering, ASTHelpers.getSymbol(tree), state);
if (originalOrdering.equals(sortedAnnotations)) {
return Description.NO_MATCH;
}
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
Description.Builder description = buildDescription(originalOrdering.get(0));
fix.ifPresent(description::addFix);
return description.build();
return describeMatch(
originalOrdering.get(0), fixOrdering(originalOrdering, sortedAnnotations, state));
}
private static ImmutableList<? extends AnnotationTree> sort(
List<? extends AnnotationTree> annotations, VisitorState state) {
List<? extends AnnotationTree> annotations, Symbol symbol, VisitorState state) {
return annotations.stream()
.sorted(comparing(annotation -> SourceCode.treeToString(annotation, state)))
.sorted(
comparing(
(AnnotationTree annotation) ->
ASTHelpers.getAnnotationType(annotation, symbol, state),
BY_ANNOTATION_TYPE)
.thenComparing(annotation -> SourceCode.treeToString(annotation, state)))
.collect(toImmutableList());
}
private static Optional<Fix> tryFixOrdering(
private static Fix fixOrdering(
List<? extends AnnotationTree> originalAnnotations,
ImmutableList<? extends AnnotationTree> sortedAnnotations,
VisitorState state) {
@@ -77,6 +93,7 @@ public final class LexicographicalAnnotationListing extends BugChecker
SuggestedFix.builder()
.replace(original, SourceCode.treeToString(replacement, state)))
.reduce(SuggestedFix.Builder::merge)
.map(SuggestedFix.Builder::build);
.map(SuggestedFix.Builder::build)
.orElseThrow(() -> new VerifyException("No annotations were provided"));
}
}

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",
@@ -60,6 +65,9 @@ import javax.lang.model.element.Name;
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link MethodReferenceUsage} instance. */
public MethodReferenceUsage() {}
@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
/*
@@ -122,7 +130,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
return Optional.empty();
}
Symbol sym = ASTHelpers.getSymbol(methodSelect);
if (!sym.isStatic()) {
if (!ASTHelpers.isStatic(sym)) {
return constructFix(lambdaExpr, "this", methodSelect);
}
return constructFix(lambdaExpr, sym.owner, methodSelect);
@@ -192,7 +200,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
Name sName = target.getSimpleName();
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
if (!"java.lang".equals(target.packge().toString())) {
if (!"java.lang".equals(ASTHelpers.enclosingPackage(target).toString())) {
Name fqName = target.getQualifiedName();
if (!sName.equals(fqName)) {
return fix.map(b -> b.addImport(fqName.toString()));

View File

@@ -40,6 +40,9 @@ public final class MissingRefasterAnnotation extends BugChecker implements Class
isType("com.google.errorprone.refaster.annotation.BeforeTemplate"),
isType("com.google.errorprone.refaster.annotation.AfterTemplate")));
/** Instantiates a new {@link MissingRefasterAnnotation} instance. */
public MissingRefasterAnnotation() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
long methodTypes =

View File

@@ -36,6 +36,9 @@ public final class MockitoStubbing extends BugChecker implements MethodInvocatio
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
/** Instantiates a new {@link MockitoStubbing} instance. */
public MockitoStubbing() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();

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,21 +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

@@ -76,6 +76,9 @@ public final class NonEmptyMono extends BugChecker implements MethodInvocationTr
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
/** Instantiates a new {@link NonEmptyMono} instance. */
public NonEmptyMono() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!MONO_SIZE_CHECK.matches(tree, state)) {

View File

@@ -70,6 +70,9 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
.named("thenComparing")
.withParameters(Function.class.getName()));
/** Instantiates a new {@link PrimitiveComparison} instance. */
public PrimitiveComparison() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isStatic = STATIC_COMPARISON_METHOD.matches(tree, state);

View File

@@ -141,7 +141,7 @@ public final class RedundantStringConversion extends BugChecker
private final Matcher<MethodInvocationTree> conversionMethodMatcher;
/** Instantiates the default {@link RedundantStringConversion}. */
/** Instantiates a default {@link RedundantStringConversion} instance. */
public RedundantStringConversion() {
this(ErrorProneFlags.empty());
}

View File

@@ -37,6 +37,9 @@ public final class RefasterAnyOfUsage extends BugChecker implements MethodInvoca
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
/** Instantiates a new {@link RefasterAnyOfUsage} instance. */
public RefasterAnyOfUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (REFASTER_ANY_OF.matches(tree, state)) {

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)
@@ -48,6 +48,9 @@ public final class RefasterRuleModifiers extends BugChecker
private static final Matcher<Tree> REFASTER_METHOD =
anyOf(BEFORE_TEMPLATE_METHOD, AFTER_TEMPLATE_METHOD, PLACEHOLDER_METHOD);
/** Instantiates a new {@link RefasterRuleModifiers} instance. */
public RefasterRuleModifiers() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
if (!hasMatchingMember(tree, BEFORE_TEMPLATE_METHOD, state)) {

View File

@@ -83,6 +83,9 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
isSameType("org.springframework.web.util.UriBuilder"),
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
/** Instantiates a new {@link RequestMappingAnnotation} instance. */
public RequestMappingAnnotation() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Auto-add `@RequestParam` where applicable.

View File

@@ -37,6 +37,9 @@ public final class RequestParamType extends BugChecker implements VariableTreeMa
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)));
/** Instantiates a new {@link RequestParamType} instance. */
public RequestParamType() {}
@Override
public Description matchVariable(VariableTree tree, VisitorState state) {
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)

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
@@ -46,9 +47,13 @@ public final class ScheduledTransactionTrace extends BugChecker implements Metho
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
/** Instantiates a new {@link ScheduledTransactionTrace} instance. */
public ScheduledTransactionTrace() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_SCHEDULED.matches(tree, state)) {
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|| !IS_SCHEDULED.matches(tree, state)) {
return Description.NO_MATCH;
}

View File

@@ -48,6 +48,9 @@ public final class Slf4jLogStatement extends BugChecker implements MethodInvocat
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("trace", "debug", "info", "warn", "error");
/** Instantiates a new {@link Slf4jLogStatement} instance. */
public Slf4jLogStatement() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!SLF4J_LOGGER_INVOCATION.matches(tree, state)) {

View File

@@ -57,6 +57,9 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
.put("PUT", "PutMapping")
.build();
/** Instantiates a new {@link SpringMvcAnnotation} instance. */
public SpringMvcAnnotation() {}
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
// XXX: We could remove the `@RequestMapping` import if not other usages remain.

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
@@ -190,6 +191,9 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
"of",
"valueOf");
/** Instantiates a new {@link StaticImport} instance. */
public StaticImport() {}
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
if (!isCandidateContext(state) || !isCandidate(tree)) {

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;
@@ -26,12 +27,12 @@ import com.sun.tools.javac.util.Convert;
import java.util.Formattable;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nullable;
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 {
@@ -52,6 +54,9 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
Suppliers.typeFromClass(CharSequence.class);
private static final Supplier<Type> FORMATTABLE_TYPE = Suppliers.typeFromClass(Formattable.class);
/** Instantiates a new {@link StringJoin} instance. */
public StringJoin() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!STRING_FORMAT_INVOCATION.matches(tree, state)) {

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,10 +61,16 @@ 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());
/** Instantiates a new {@link TimeZoneUsage} instance. */
public TimeZoneUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return BANNED_TIME_METHOD.matches(tree, state)

View File

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

View File

@@ -21,6 +21,9 @@ public final class MethodMatcherFactory {
private static final Pattern METHOD_SIGNATURE =
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
/** Instantiates a new {@link MethodMatcherFactory} instance. */
public MethodMatcherFactory() {}
/**
* Creates a {@link Matcher} of methods with any of the given signatures.
*

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,4 +1,4 @@
/** Auxiliary utilities for use by Error Prone checks. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
@org.jspecify.nullness.NullMarked
package tech.picnic.errorprone.bugpatterns.util;

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

@@ -13,21 +13,23 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
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 com.sun.source.tree.Tree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
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;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/**
@@ -46,36 +48,30 @@ final class AssortedRules {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
int after(int index, int size) {
return checkIndex(index, size);
}
}
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
// not allow an empty non-EnumMap to be provided.
static final class CreateEnumMap<K extends Enum<K>, V> {
/**
* Prefer {@link Objects#checkIndex(int, int)} over less descriptive or more verbose alternatives.
*
* <p>If a custom error message is desired, consider using Guava's {@link
* com.google.common.base.Preconditions#checkElementIndex(int, int, String)}.
*/
static final class CheckIndexConditional {
@BeforeTemplate
Map<K, V> before() {
return new HashMap<>();
void before(int index, int size) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException();
}
}
@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);
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(int index, int size) {
checkIndex(index, size);
}
}
@@ -112,8 +108,7 @@ final class AssortedRules {
}
@AfterTemplate
@Nullable
T after(Iterator<T> iterator, T defaultValue) {
@Nullable T after(Iterator<T> iterator, T defaultValue) {
return Iterators.getNext(iterator, defaultValue);
}
}
@@ -205,32 +200,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
@@ -246,6 +215,29 @@ final class AssortedRules {
}
}
@SuppressWarnings("serial")
static final class DescribeMatch extends BugChecker {
@BeforeTemplate
Description before(Tree tree) {
return buildDescription(tree).build();
}
@BeforeTemplate
Description before(DiagnosticPosition tree) {
return buildDescription(tree).build();
}
@BeforeTemplate
Description before(JCTree tree) {
return buildDescription(tree).build();
}
@AfterTemplate
Description after(Tree tree) {
return describeMatch(tree);
}
}
// /**
// * Don't unnecessarily pass a method reference to {@link Supplier#get()} or wrap this method
// * in a lambda expression.

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

@@ -7,7 +7,7 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Collection;
import java.util.Set;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Multimap}s. */
@@ -50,8 +50,7 @@ final class MultimapRules {
*/
static final class MultimapGet<K, V> {
@BeforeTemplate
@Nullable
Collection<V> before(Multimap<K, V> multimap, K key) {
@Nullable Collection<V> before(Multimap<K, V> multimap, K key) {
return Refaster.anyOf(multimap.asMap(), Multimaps.asMap(multimap)).get(key);
}

View File

@@ -9,7 +9,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with (possibly) null values. */

View File

@@ -16,7 +16,7 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Optional}s. */
@@ -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

@@ -0,0 +1,176 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.base.Preconditions;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster templates related to statements dealing with {@link Preconditions}. */
@OnlineDocumentation
final class PreconditionsRules {
private PreconditionsRules() {}
/** Prefer {@link Preconditions#checkArgument(boolean)} over more verbose alternatives. */
static final class CheckArgument {
@BeforeTemplate
void before(boolean condition) {
if (condition) {
throw new IllegalArgumentException();
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean condition) {
checkArgument(!condition);
}
}
/** Prefer {@link Preconditions#checkArgument(boolean, Object)} over more verbose alternatives. */
static final class CheckArgumentWithMessage {
@BeforeTemplate
void before(boolean condition, String message) {
if (condition) {
throw new IllegalArgumentException(message);
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean condition, String message) {
checkArgument(!condition, message);
}
}
/**
* Prefer {@link Preconditions#checkElementIndex(int, int, String)} over less descriptive or more
* verbose alternatives.
*
* <p>Note that the two-argument {@link Preconditions#checkElementIndex(int, int)} is better
* replaced with {@link java.util.Objects#checkIndex(int, int)}.
*/
static final class CheckElementIndexWithMessage {
@BeforeTemplate
void before(int index, int size, String message) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(message);
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(int index, int size, String message) {
checkElementIndex(index, size, message);
}
}
/** Prefer {@link Preconditions#checkNotNull(Object)} over more verbose alternatives. */
static final class CheckNotNull<T> {
@BeforeTemplate
void before(T object) {
if (object == null) {
throw new NullPointerException();
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object) {
checkNotNull(object);
}
}
/** Prefer {@link Preconditions#checkNotNull(Object, Object)} over more verbose alternatives. */
static final class CheckNotNullWithMessage<T> {
@BeforeTemplate
void before(T object, String message) {
if (object == null) {
throw new NullPointerException(message);
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object, String message) {
checkNotNull(object, message);
}
}
/**
* Prefer {@link Preconditions#checkPositionIndex(int, int)} over less descriptive or more verbose
* alternatives.
*/
static final class CheckPositionIndex {
@BeforeTemplate
void before(int index, int size) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException();
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(int index, int size) {
checkPositionIndex(index, size);
}
}
/**
* Prefer {@link Preconditions#checkPositionIndex(int, int, String)} over less descriptive or more
* verbose alternatives.
*/
static final class CheckPositionIndexWithMessage {
@BeforeTemplate
void before(int index, int size, String message) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException(message);
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(int index, int size, String message) {
checkPositionIndex(index, size, message);
}
}
/** Prefer {@link Preconditions#checkState(boolean)} over more verbose alternatives. */
static final class CheckState {
@BeforeTemplate
void before(boolean condition) {
if (condition) {
throw new IllegalStateException();
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean condition) {
checkState(!condition);
}
}
/** Prefer {@link Preconditions#checkState(boolean, Object)} over more verbose alternatives. */
static final class CheckStateWithMessage {
@BeforeTemplate
void before(boolean condition, String message) {
if (condition) {
throw new IllegalStateException(message);
}
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean condition, String message) {
checkState(!condition, message);
}
}
}

View File

@@ -1,10 +1,13 @@
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;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
@@ -14,8 +17,11 @@ import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.time.Duration;
import java.util.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;
@@ -25,7 +31,11 @@ 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. */
@@ -68,6 +78,91 @@ final class ReactorRules {
}
}
/**
* 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
@@ -141,6 +236,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
@@ -171,7 +320,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
@@ -188,7 +340,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
@@ -240,16 +394,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));
}
}
@@ -257,7 +590,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
@@ -274,9 +608,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
@@ -312,6 +644,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.
@@ -349,6 +707,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
@@ -375,6 +769,240 @@ 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.
static final class ContextEmpty {
@BeforeTemplate
Context before() {
return Context.of(Refaster.anyOf(new HashMap<>(), ImmutableMap.of()));
}
@AfterTemplate
Context after() {
return Context.empty();
}
}
/** Prefer {@link PublisherProbe#empty()}} over more verbose alternatives. */
static final class PublisherProbeEmpty<T> {
@BeforeTemplate

View File

@@ -168,8 +168,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

@@ -16,7 +16,7 @@ import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link String}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

@@ -1,4 +1,4 @@
/** Picnic Refaster rules. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
@org.jspecify.nullness.NullMarked
package tech.picnic.errorprone.refasterrules;

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

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
@@ -9,9 +7,7 @@ import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.expectErrorMessage(
"X", containsPattern("Sort annotations lexicographically where possible"));
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationListing.class, getClass());
@@ -21,7 +17,9 @@ final class LexicographicalAnnotationListingTest {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.lang.annotation.ElementType;",
"import java.lang.annotation.Repeatable;",
"import java.lang.annotation.Target;",
"",
"interface A {",
" @Repeatable(Foos.class)",
@@ -33,6 +31,7 @@ final class LexicographicalAnnotationListingTest {
" Bar[] anns() default {};",
" }",
"",
" @Target(ElementType.METHOD)",
" @interface Bar {",
" String[] value() default {};",
" }",
@@ -45,11 +44,21 @@ final class LexicographicalAnnotationListingTest {
" Foo[] value();",
" }",
"",
" // BUG: Diagnostic matches: X",
" @Target(ElementType.TYPE_USE)",
" @interface FooTypeUse {",
" String[] value() default {};",
" }",
"",
" @Target(ElementType.TYPE_USE)",
" @interface BarTypeUse {",
" String[] value() default {};",
" }",
"",
" // BUG: Diagnostic contains:",
" @Foo",
" @Bar",
" A unsortedSimpleCase();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Foo()",
" @Bar()",
" A unsortedWithParens();",
@@ -61,12 +70,12 @@ final class LexicographicalAnnotationListingTest {
" @Foo()",
" A sortedAnnotationsOneWithParens();",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Foo",
" @Baz",
" @Bar",
" A threeUnsortedAnnotationsSameInitialLetter();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Bar",
" @Foo()",
" @Baz",
@@ -77,16 +86,16 @@ final class LexicographicalAnnotationListingTest {
" @Foo()",
" A threeSortedAnnotations();",
"",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Foo({\"b\"})",
" @Bar({\"a\"})",
" A unsortedWithStringAttributes();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Baz(str = {\"a\", \"b\"})",
" @Foo(ints = {1, 0})",
" @Bar",
" A unsortedWithAttributes();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Bar",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Baz",
@@ -101,11 +110,23 @@ final class LexicographicalAnnotationListingTest {
" @Foo(ints = {1, 2})",
" @Foo({\"b\"})",
" A sortedRepeatableAnnotation();",
" // BUG: Diagnostic matches: X",
" // BUG: Diagnostic contains:",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Bar",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"",
" // BUG: Diagnostic contains:",
" default @FooTypeUse @BarTypeUse A unsortedTypeAnnotations() {",
" return null;",
" }",
"",
" // BUG: Diagnostic contains:",
" @Baz",
" @Bar",
" default @FooTypeUse @BarTypeUse A unsortedTypeUseAndOtherAnnotations() {",
" return null;",
" }",
"}")
.doTest();
}
@@ -115,7 +136,9 @@ final class LexicographicalAnnotationListingTest {
refactoringTestHelper
.addInputLines(
"A.java",
"import java.lang.annotation.ElementType;",
"import java.lang.annotation.Repeatable;",
"import java.lang.annotation.Target;",
"",
"interface A {",
" @Repeatable(Foos.class)",
@@ -127,6 +150,7 @@ final class LexicographicalAnnotationListingTest {
" Bar[] anns() default {};",
" }",
"",
" @Target(ElementType.METHOD)",
" @interface Bar {",
" String[] value() default {};",
" }",
@@ -139,6 +163,16 @@ final class LexicographicalAnnotationListingTest {
" Foo[] value();",
" }",
"",
" @Target(ElementType.TYPE_USE)",
" @interface FooTypeUse {",
" String[] value() default {};",
" }",
"",
" @Target(ElementType.TYPE_USE)",
" @interface BarTypeUse {",
" String[] value() default {};",
" }",
"",
" @Bar",
" A singleAnnotation();",
"",
@@ -174,10 +208,18 @@ final class LexicographicalAnnotationListingTest {
" @Bar",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"",
" @Baz",
" @Bar",
" default @FooTypeUse @BarTypeUse A unsortedWithTypeUseAnnotations() {",
" return null;",
" }",
"}")
.addOutputLines(
"A.java",
"import java.lang.annotation.ElementType;",
"import java.lang.annotation.Repeatable;",
"import java.lang.annotation.Target;",
"",
"interface A {",
" @Repeatable(Foos.class)",
@@ -189,6 +231,7 @@ final class LexicographicalAnnotationListingTest {
" Bar[] anns() default {};",
" }",
"",
" @Target(ElementType.METHOD)",
" @interface Bar {",
" String[] value() default {};",
" }",
@@ -201,6 +244,16 @@ final class LexicographicalAnnotationListingTest {
" Foo[] value();",
" }",
"",
" @Target(ElementType.TYPE_USE)",
" @interface FooTypeUse {",
" String[] value() default {};",
" }",
"",
" @Target(ElementType.TYPE_USE)",
" @interface BarTypeUse {",
" String[] value() default {};",
" }",
"",
" @Bar",
" A singleAnnotation();",
"",
@@ -236,6 +289,12 @@ final class LexicographicalAnnotationListingTest {
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"",
" @Bar",
" @Baz",
" default @BarTypeUse @FooTypeUse A unsortedWithTypeUseAnnotations() {",
" return null;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -19,7 +19,7 @@ final class RequestParamTypeTest {
"import java.util.List;",
"import java.util.Map;",
"import java.util.Set;",
"import javax.annotation.Nullable;",
"import org.jspecify.nullness.Nullable;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PostMapping;",

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

@@ -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,188 @@
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 {
private static final ImmutableSet<Supplier<Type>> TYPES =
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"))))));
@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 contained in
* {@link #TYPES}.
*/
@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 : TYPES) {
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();
}
}
}

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,10 +52,12 @@ final class RefasterRulesTest {
JUnitRules.class,
LongStreamRules.class,
MapEntryRules.class,
MapRules.class,
MockitoRules.class,
MultimapRules.class,
NullRules.class,
OptionalRules.class,
PreconditionsRules.class,
PrimitiveRules.class,
ReactorRules.class,
RxJava2AdapterRules.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,17 @@ 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 com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
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 +24,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -37,12 +37,10 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
return Preconditions.checkElementIndex(0, 1);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new HashMap<>();
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
void testCheckIndexConditional() {
if (1 < 0 || 1 >= 2) {
throw new IndexOutOfBoundsException();
}
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
@@ -89,17 +87,25 @@ 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")),
Splitter.on(',').splitToList(new StringBuilder("bar")).stream());
}
BugChecker testDescribeMatch() {
return new BugChecker() {
Description testDescribeMatch(DiagnosticPosition diagnosticPosition) {
return buildDescription(diagnosticPosition).build();
}
Description testDescribeMatch(JCTree tree) {
return buildDescription(tree).build();
}
Description testDescribeMatch(Tree tree) {
return buildDescription(tree).build();
}
};
}
}

View File

@@ -2,24 +2,24 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.toImmutableEnumSet;
import static java.util.Objects.checkIndex;
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 com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
@@ -27,7 +27,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
HashMap.class,
HashSet.class,
Iterables.class,
Preconditions.class,
@@ -38,15 +37,11 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
}
int testCheckIndex() {
return Objects.checkIndex(0, 1);
return checkIndex(0, 1);
}
Map<RoundingMode, String> testCreateEnumMap() {
return new EnumMap<>(RoundingMode.class);
}
String testMapGetOrNull() {
return ImmutableMap.of(1, "foo").get("bar");
void testCheckIndexConditional() {
checkIndex(1, 2);
}
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
@@ -91,17 +86,25 @@ 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"),
Splitter.on(',').splitToStream(new StringBuilder("bar")));
}
BugChecker testDescribeMatch() {
return new BugChecker() {
Description testDescribeMatch(DiagnosticPosition diagnosticPosition) {
return describeMatch(diagnosticPosition);
}
Description testDescribeMatch(JCTree tree) {
return describeMatch(tree);
}
Description testDescribeMatch(Tree tree) {
return describeMatch(tree);
}
};
}
}

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

@@ -0,0 +1,59 @@
package tech.picnic.errorprone.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PreconditionsRulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgument() {
if ("foo".isEmpty()) {
throw new IllegalArgumentException();
}
}
void testCheckArgumentWithMessage() {
if ("foo".isEmpty()) {
throw new IllegalArgumentException("The string is empty");
}
}
void testCheckElementIndexWithMessage() {
if (1 < 0 || 1 >= 2) {
throw new IndexOutOfBoundsException("My index");
}
}
void testCheckNotNull() {
if ("foo" == null) {
throw new NullPointerException();
}
}
void testCheckNotNullWithMessage() {
if ("foo" == null) {
throw new NullPointerException("The string is null");
}
}
void testCheckPositionIndex() {
if (1 < 0 || 1 > 2) {
throw new IndexOutOfBoundsException();
}
}
void testCheckPositionIndexWithMessage() {
if (1 < 0 || 1 > 2) {
throw new IndexOutOfBoundsException("My position");
}
}
void testCheckState() {
if ("foo".isEmpty()) {
throw new IllegalStateException();
}
}
void testCheckStateWithMessage() {
if ("foo".isEmpty()) {
throw new IllegalStateException("The string is empty");
}
}
}

View File

@@ -0,0 +1,47 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PreconditionsRulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgument() {
checkArgument(!"foo".isEmpty());
}
void testCheckArgumentWithMessage() {
checkArgument(!"foo".isEmpty(), "The string is empty");
}
void testCheckElementIndexWithMessage() {
checkElementIndex(1, 2, "My index");
}
void testCheckNotNull() {
checkNotNull("foo");
}
void testCheckNotNullWithMessage() {
checkNotNull("foo", "The string is null");
}
void testCheckPositionIndex() {
checkPositionIndex(1, 2);
}
void testCheckPositionIndexWithMessage() {
checkPositionIndex(1, 2, "My position");
}
void testCheckState() {
checkState(!"foo".isEmpty());
}
void testCheckStateWithMessage() {
checkState(!"foo".isEmpty(), "The string is empty");
}
}

View File

@@ -1,10 +1,13 @@
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;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.time.Duration;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
@@ -12,12 +15,14 @@ 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.test.RefasterRuleCollectionTestCase;
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(assertThat(0));
return ImmutableSet.of(assertThat(0), HashMap.class, ImmutableMap.class);
}
ImmutableSet<Mono<?>> testMonoFromSupplier() {
@@ -35,6 +40,26 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromSupplier(() -> Optional.of(2).orElse(null)));
}
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()));
}
@@ -55,6 +80,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());
}
@@ -66,12 +105,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() {
@@ -83,17 +126,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() {
@@ -104,6 +212,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)),
@@ -116,6 +232,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());
}
@@ -126,6 +250,67 @@ 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()));
}
ImmutableSet<PublisherProbe<Void>> testPublisherProbeEmpty() {
return ImmutableSet.of(PublisherProbe.of(Mono.empty()), PublisherProbe.of(Flux.empty()));
}

View File

@@ -3,23 +3,29 @@ 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;
import com.google.common.collect.ImmutableSet;
import java.time.Duration;
import java.util.HashMap;
import java.util.Optional;
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 {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(assertThat(0));
return ImmutableSet.of(assertThat(0), HashMap.class, ImmutableMap.class);
}
ImmutableSet<Mono<?>> testMonoFromSupplier() {
@@ -37,6 +43,29 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.defer(() -> Mono.justOrEmpty(Optional.of(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());
}
@@ -57,6 +86,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);
}
@@ -66,12 +108,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() {
@@ -83,17 +130,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() {
@@ -104,6 +211,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()),
@@ -116,6 +231,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();
}
@@ -124,6 +247,65 @@ 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());
}
ImmutableSet<PublisherProbe<Void>> testPublisherProbeEmpty() {
return ImmutableSet.of(PublisherProbe.empty(), PublisherProbe.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();
}

135
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.4.0</version>
<version>0.5.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -48,7 +48,7 @@
<scm>
<developerConnection>scm:git:git@github.com:PicnicSupermarket/error-prone-support.git</developerConnection>
<tag>v0.4.0</tag>
<tag>HEAD</tag>
<url>https://github.com/PicnicSupermarket/error-prone-support</url>
</scm>
<issueManagement>
@@ -146,26 +146,18 @@
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-3</version.error-prone-fork>
<version.error-prone-orig>2.15.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.15</version.error-prone-slf4j>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.16</version.error-prone-orig>
<version.error-prone-slf4j>0.1.16</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.8.6</version.maven>
<version.mockito>4.8.0</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.1</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</version>
<version>2.14.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -243,11 +235,6 @@
<artifactId>auto-value-annotations</artifactId>
<version>${version.auto-value}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
@@ -282,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. -->
@@ -308,7 +281,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.23</version>
<version>2022.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -320,12 +293,12 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.7</version>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.3</version>
<version>2.2.7</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -370,7 +343,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.26.0</version>
<version>3.27.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -404,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.4</version>
<version>2.7.6</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -456,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>
@@ -628,11 +611,15 @@
</property>
<property name="illegalClasses" value="com.mongodb.lang.Nullable">
<!-- Instead, please use
`javax.annotation.Nullable`. -->
`org.jspecify.nullness.Nullable`. -->
</property>
<property name="illegalClasses" value="io.micrometer.core.lang.Nullable">
<!-- Instead, please use
`javax.annotation.Nullable`. -->
`org.jspecify.nullness.Nullable`. -->
</property>
<property name="illegalClasses" value="javax.annotation.Nullable">
<!-- Instead, please use
`org.jspecify.nullness.Nullable`. -->
</property>
<property name="illegalClasses" value="javax.annotation.concurrent.Immutable">
<!-- Instead, please use
@@ -648,7 +635,7 @@
</property>
<property name="illegalClasses" value="org.springframework.lang.Nullable">
<!-- Instead, please use
`javax.annotation.Nullable`. -->
`org.jspecify.nullness.Nullable`. -->
</property>
<property name="illegalPkgs" value="com.amazonaws.annotation" />
<property name="illegalPkgs" value="com.beust.jcommander.internal" />
@@ -784,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>
@@ -845,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>
@@ -897,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>
@@ -985,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>
@@ -1217,17 +1190,20 @@
<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>
<artifactId>versions-maven-plugin</artifactId>
<version>2.12.0</version>
<version>2.13.0</version>
<configuration>
<updateBuildOutputTimestampPolicy>never</updateBuildOutputTimestampPolicy>
</configuration>
</plugin>
<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
@@ -1263,11 +1239,15 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.9.8</version>
<version>1.9.11</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
<excludedClass>*.AutoValue_*</excludedClass>
<!-- Refaster rule collections. -->
<excludedClass>*.refaster*.*Rules*</excludedClass>
</excludedClasses>
<failWhenNoMutations>false</failWhenNoMutations>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
contention. -->
@@ -1276,6 +1256,11 @@
<timestampedReports>false</timestampedReports>
</configuration>
<dependencies>
<dependency>
<groupId>com.groupcdg</groupId>
<artifactId>pitest-git-plugin</artifactId>
<version>${version.pitest-git}</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.0</version>
<version>0.5.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-compiler</artifactId>
@@ -36,14 +36,14 @@
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@@ -12,6 +12,9 @@ import com.sun.tools.javac.api.BasicJavacTask;
*/
@AutoService(Plugin.class)
public final class RefasterRuleCompiler implements Plugin {
/** Instantiates a new {@link RefasterRuleCompiler} instance. */
public RefasterRuleCompiler() {}
@Override
public String getName() {
return getClass().getSimpleName();

View File

@@ -28,10 +28,10 @@ import java.io.ObjectOutputStream;
import java.io.UncheckedIOException;
import java.lang.annotation.Annotation;
import java.util.Map;
import javax.annotation.Nullable;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.StandardLocation;
import org.jspecify.nullness.Nullable;
import tech.picnic.errorprone.refaster.AnnotatedCompositeCodeTransformer;
/**
@@ -71,10 +71,10 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
private ImmutableMap<ClassTree, CodeTransformer> compileRefasterRules(ClassTree tree) {
ImmutableMap.Builder<ClassTree, CodeTransformer> rules = ImmutableMap.builder();
new TreeScanner<Void, ImmutableClassToInstanceMap<Annotation>>() {
@Nullable
new TreeScanner<@Nullable Void, ImmutableClassToInstanceMap<Annotation>>() {
@Override
public Void visitClass(ClassTree node, ImmutableClassToInstanceMap<Annotation> annotations) {
public @Nullable Void visitClass(
ClassTree node, ImmutableClassToInstanceMap<Annotation> annotations) {
ClassSymbol symbol = ASTHelpers.getSymbol(node);
ImmutableList<CodeTransformer> transformers =
@@ -105,7 +105,7 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
private static boolean containsRefasterRules(ClassTree tree) {
return Boolean.TRUE.equals(
new TreeScanner<Boolean, Void>() {
new TreeScanner<Boolean, @Nullable Void>() {
@Override
public Boolean visitAnnotation(AnnotationTree node, @Nullable Void unused) {
Symbol sym = ASTHelpers.getSymbol(node);

View File

@@ -3,5 +3,5 @@
* files on the classpath.
*/
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
@org.jspecify.nullness.NullMarked
package tech.picnic.errorprone.refaster.plugin;

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