Compare commits

..

50 Commits

Author SHA1 Message Date
Benedek Halasi
24dbe77522 xxx 2024-04-09 09:56:06 +02:00
Rick Ossendrijver
2311bd54b1 Add openrewrite dependencies 2024-04-09 09:00:28 +02:00
Rick Ossendrijver
fcd708e00f Update the workshop branch and apply best practices of EPS 2024-04-05 10:08:52 +02:00
Rick Ossendrijver
244ac55a01 Post-rebase fix 2024-04-05 07:58:59 +02:00
Rick Ossendrijver
ec36dcab72 Introduce workshop module and configure integration-test 2024-04-05 07:58:58 +02:00
Picnic-Bot
3d51acd613 Upgrade MongoDB driver 5.0.0 -> 5.0.1 (#1121)
See:
- https://jira.mongodb.org/issues/?jql=project%20%3D%20JAVA%20AND%20fixVersion%20%3E%205.0.0%20AND%20fixVersion%20%3C%3D%205.0.1
- https://github.com/mongodb/mongo-java-driver/releases/tag/r5.0.1
- https://github.com/mongodb/mongo-java-driver/compare/r5.0.0...r5.0.1
2024-04-04 14:48:14 +02:00
Picnic-Bot
d2fb576ecc Upgrade jacoco-maven-plugin 0.8.11 -> 0.8.12 (#1122)
See:
- https://github.com/jacoco/jacoco/releases/tag/v0.8.12
- https://github.com/jacoco/jacoco/compare/v0.8.11...v0.8.12
2024-04-04 14:31:03 +02:00
Stephan Schroevers
d658901231 Upgrade Error Prone fork v2.26.1-picnic-1 -> v2.26.1-picnic-2 (#1119)
See:
- https://github.com/PicnicSupermarket/error-prone/releases/tag/v2.26.1-picnic-2
- https://github.com/PicnicSupermarket/error-prone/compare/v2.26.1-picnic-1...v2.26.1-picnic-2
2024-04-03 16:46:24 +02:00
Picnic-Bot
76d1ca7bdf Upgrade Checkstyle 10.14.2 -> 10.15.0 (#1118)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.15.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.14.2...checkstyle-10.15.0
2024-04-03 11:22:32 +02:00
Picnic-Bot
341977b227 Upgrade Google Java Format 1.21.0 -> 1.22.0 (#1120)
See:
- https://github.com/google/google-java-format/releases/tag/v1.22.0
- https://github.com/google/google-java-format/compare/v1.21.0...v1.22.0
2024-04-03 08:27:53 +02:00
Rick Ossendrijver
75872dc2f5 Introduce Cody as IntelliJ icon (#1075) 2024-03-30 19:18:42 +01:00
Picnic-Bot
b609537a52 Upgrade pomchecker-maven-plugin 1.10.0 -> 1.11.0 (#1112)
See:
- https://github.com/kordamp/pomchecker/releases/tag/v1.11.0
- https://github.com/kordamp/pomchecker/compare/v1.10.0...v1.11.0
2024-03-30 15:09:54 +01:00
Picnic-Bot
1469d1e157 Upgrade maven-gpg-plugin 3.2.1 -> 3.2.2 (#1113)
See:
- https://github.com/apache/maven-gpg-plugin/releases/tag/maven-gpg-plugin-3.2.2
- https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.2.1...maven-gpg-plugin-3.2.2
2024-03-30 14:02:27 +01:00
Picnic-Bot
111b7d04f2 Upgrade actions/configure-pages v4.0.0 -> v5.0.0 (#1117)
See:
- https://github.com/actions/configure-pages/releases/tag/v5.0.0
2024-03-30 13:53:38 +01:00
Picnic-Bot
9e297df1c7 Upgrade errorprone-slf4j 0.1.22 -> 0.1.23 (#1111)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.23
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.22...v0.1.23
2024-03-30 13:27:28 +01:00
Picnic-Bot
7babb48751 Upgrade NullAway 0.10.24 -> 0.10.25 (#1116)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/releases/tag/v0.10.25
- https://github.com/uber/NullAway/compare/v0.10.24...v0.10.25
2024-03-30 12:46:03 +01:00
Picnic-Bot
dfaffacbb5 Upgrade Byte Buddy 1.14.12 -> 1.14.13 (#1115)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.13
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.12...byte-buddy-1.14.13
2024-03-30 12:26:44 +01:00
Stephan Schroevers
769779cf21 Introduce Refaster rules that resolve EnumOrdinal violations (#1104) 2024-03-27 15:44:19 +01:00
Rick Ossendrijver
9d8a5af44a Start release notes with "Update considerations and deprecations" section (#1105) 2024-03-25 14:19:31 +01:00
Picnic-Bot
8a84acca7b Upgrade Forbidden APIs plugin 3.6 -> 3.7 (#1107)
See:
- https://github.com/policeman-tools/forbidden-apis/wiki/Changes
- https://github.com/policeman-tools/forbidden-apis/compare/3.6...3.7
2024-03-25 10:41:39 +01:00
Picnic-Bot
b551f90d38 Upgrade dawidd6/action-download-artifact v3.1.2 -> v3.1.4 (#1109)
See:
- https://github.com/dawidd6/action-download-artifact/releases/tag/v3.1.4
- https://github.com/dawidd6/action-download-artifact/releases/tag/v3.1.3
2024-03-25 10:12:27 +01:00
Picnic-Bot
789a9cc0aa Upgrade AspectJ 1.9.21.2 -> 1.9.22 (#1106)
See:
- https://github.com/eclipse-aspectj/aspectj/releases/tag/V1_9_22
- https://github.com/eclipse-aspectj/aspectj/compare/V1_9_21_2...V1_9_22
2024-03-25 09:53:18 +01:00
Picnic-Bot
13e35338af Upgrade ruby/setup-ruby v1.172.0 -> v1.173.0 (#1110)
See https://github.com/ruby/setup-ruby/releases/tag/v1.173.0
2024-03-25 09:43:35 +01:00
Stephan Schroevers
281a003dd7 Introduce OptionalOrElse check (#1024)
While there, extend the `OptionalIdentity` Refaster rule to
automatically resolve one class of `NestedOptionals` violations.
2024-03-25 09:29:17 +01:00
Picnic-Bot
e40df7e1b8 Upgrade CodeQL v3.24.5 -> v3.24.9 (#1108)
See:
- https://github.com/github/codeql-action/blob/main/CHANGELOG.md
- https://github.com/github/codeql-action/compare/v3.24.8...v3.24.9
- https://github.com/github/codeql-action/compare/v3.24.7...v3.24.8
- https://github.com/github/codeql-action/compare/v3.24.6...v3.24.7
- https://github.com/github/codeql-action/compare/v3.24.5...v3.24.6
2024-03-25 08:42:59 +01:00
Picnic-Bot
bb2b1e6034 Upgrade Spring Boot 3.2.3 -> 3.2.4 (#1103)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v3.2.4
- https://github.com/spring-projects/spring-boot/compare/v3.2.3...v3.2.4
2024-03-23 12:49:26 +01:00
Stephan Schroevers
f8cac19330 Compact and replace StreamIs{,Not}Empty Refaster rules (#1028)
The new `StreamFindAnyIs{Empty,Present}` rules are simpler thanks to the
use of `@AlsoNegation`. In some cases an additional application of the
`OptionalIsEmpty` rule will be required.
2024-03-22 08:23:18 +01:00
Picnic-Bot
52fe79c343 Upgrade Swagger 2.2.20 -> 2.2.21 (#1102)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.21
- https://github.com/swagger-api/swagger-core/compare/v2.2.20...v2.2.21
2024-03-22 08:11:48 +01:00
Picnic-Bot
0b696b95b6 Upgrade maven-compiler-plugin 3.12.1 -> 3.13.0 (#1101)
See:
- https://github.com/apache/maven-compiler-plugin/releases/tag/maven-compiler-plugin-3.13.0
- https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.12.1...maven-compiler-plugin-3.13.0
2024-03-21 10:39:37 +01:00
Picnic-Bot
63bc903f83 Upgrade Spring Security 6.2.2 -> 6.2.3 (#1093)
See:
- https://github.com/spring-projects/spring-security/releases/tag/6.2.3
- https://github.com/spring-projects/spring-security/compare/6.2.2...6.2.3
2024-03-21 08:17:04 +01:00
Stephan Schroevers
b166d0daea Update Error Prone compatibility matrix (#1092)
While there, improve the generation script.
2024-03-21 07:31:07 +01:00
Picnic-Bot
6914dae822 Upgrade swagger-annotations 1.6.13 -> 1.6.14 (#1100)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.13
- https://github.com/swagger-api/swagger-core/compare/v1.6.12...v1.6.13
2024-03-20 17:46:13 +01:00
Picnic-Bot
c5fb53d725 Upgrade Guava 33.0.0-jre -> 33.1.0-jre (#1086)
See:
- https://guava.dev/releases/33.1.0-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v33.1.0
- https://github.com/google/guava/compare/v33.0.0...v33.1.0
2024-03-20 10:59:23 +01:00
Picnic-Bot
d36d20da08 Upgrade OpenRewrite Templating 1.6.2 -> 1.6.3 (#1099)
See:
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.6.3
- https://github.com/openrewrite/rewrite-templating/compare/v1.6.2...v1.6.3
2024-03-20 08:35:53 +01:00
Picnic-Bot
502281f4d3 Upgrade maven-gpg-plugin 3.2.0 -> 3.2.1 (#1096)
See:
- https://github.com/apache/maven-gpg-plugin/releases/tag/maven-gpg-plugin-3.2.1
- https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.2.0...maven-gpg-plugin-3.2.1
2024-03-20 08:06:38 +01:00
Picnic-Bot
daa4f19c57 Upgrade OpenRewrite 2.8.0 -> 2.8.1 (#1098)
See:
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v2.8.1
- https://github.com/openrewrite/rewrite-recipe-bom/compare/v2.8.0...v2.8.1
2024-03-20 07:42:57 +01:00
Picnic-Bot
02f726f43c Upgrade git-commit-id-maven-plugin 8.0.1 -> 8.0.2 (#1095)
See:
- https://github.com/git-commit-id/git-commit-id-maven-plugin/releases/tag/v8.0.2
- https://github.com/git-commit-id/git-commit-id-maven-plugin/compare/v8.0.1...v8.0.2
2024-03-19 11:57:38 +01:00
Picnic-Bot
b9e8186159 Upgrade actions/deploy-pages v4.0.4 -> v4.0.5 (#1094)
See:
- https://github.com/actions/deploy-pages/releases/tag/v4.0.5
2024-03-19 10:55:56 +01:00
Picnic-Bot
85baadd5df Upgrade Error Prone 2.25.0 -> 2.26.1 (#1078)
See:
- https://github.com/google/error-prone/releases/tag/v2.26.0
- https://github.com/google/error-prone/releases/tag/v2.26.1
- https://github.com/google/error-prone/compare/v2.25.0...v2.26.1
- https://github.com/PicnicSupermarket/error-prone/compare/v2.25.0-picnic-2...v2.26.1-picnic-1
2024-03-19 08:07:43 +01:00
Picnic-Bot
ab871ec9bb Upgrade Checkstyle 10.14.0 -> 10.14.2 (#1079)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.14.1
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.14.2
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.14.0...checkstyle-10.14.2
2024-03-18 15:26:27 +01:00
Picnic-Bot
753928f4da Upgrade Project Reactor 2023.0.3 -> 2023.0.4 (#1085)
See:
- https://github.com/reactor/reactor/releases/tag/2023.0.4
- https://github.com/reactor/reactor/compare/2023.0.3...2023.0.4
2024-03-18 14:29:36 +01:00
Picnic-Bot
fe84bada33 Upgrade AspectJ 1.9.21.1 -> 1.9.21.2 (#1089)
See:
- https://github.com/eclipse-aspectj/aspectj/releases/tag/V1_9_21_2
- https://github.com/eclipse/org.aspectj/compare/V1_9_21_1...V1_9_21_2
2024-03-18 12:54:41 +01:00
Picnic-Bot
5b8d6ed9c5 Upgrade Spring 6.1.4 -> 6.1.5 (#1091)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v6.1.5
- https://github.com/spring-projects/spring-framework/compare/v6.1.4...v6.1.5
2024-03-18 11:50:49 +01:00
Picnic-Bot
2ad2fdfb0f Upgrade sortpom-maven-plugin 3.4.0 -> 3.4.1 (#1077)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.4.1
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.4.0...sortpom-parent-3.4.1
2024-03-18 11:01:03 +01:00
Picnic-Bot
a10558a044 Upgrade maven-gpg-plugin 3.1.0 -> 3.2.0 (#1082)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MGPG%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.2.0
- https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.1.0...maven-gpg-plugin-3.2.0
2024-03-18 10:00:55 +01:00
Picnic-Bot
7316d05c22 Upgrade sonar-maven-plugin 3.10.0.2594 -> 3.11.0.3922 (#1090)
See:
- https://github.com/SonarSource/sonar-scanner-maven/releases/tag/3.11.0.3922
- https://github.com/SonarSource/sonar-scanner-maven/compare/3.10.0.2594...3.11.0.3922
2024-03-18 09:43:40 +01:00
Picnic-Bot
3177db55b8 Upgrade OpenRewrite 2.7.1 -> 2.8.0 (#1081)
See:
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v2.8.0
- https://github.com/openrewrite/rewrite-recipe-bom/compare/v2.7.1...v2.8.0
2024-03-18 08:45:48 +01:00
Picnic-Bot
a6a63f9553 Upgrade Jackson 2.16.2 -> 2.17.0 (#1087)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.17
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.16.2...jackson-bom-2.17.0
2024-03-18 07:14:09 +01:00
Rick Ossendrijver
df0eb9ee2f Drop unused field in Slf4jLogStatementTest (#1084) 2024-03-17 10:49:53 +01:00
Stephan Schroevers
e3cda3ea49 [maven-release-plugin] prepare for next development iteration 2024-03-15 13:04:44 +01:00
74 changed files with 2250 additions and 119 deletions

8
.github/release.yml vendored
View File

@@ -3,16 +3,16 @@ changelog:
labels:
- "ignore-changelog"
categories:
- title: ":warning: Update considerations and deprecations"
labels:
- "breaking change"
- "deprecation"
- title: ":rocket: New Error Prone checks and Refaster rules"
labels:
- "new feature"
- title: ":sparkles: Improvements"
labels:
- "improvement"
- title: ":warning: Update considerations and deprecations"
labels:
- "breaking change"
- "deprecation"
- title: ":bug: Bug fixes"
labels:
- "bug"

View File

@@ -7,6 +7,7 @@ permissions:
contents: read
jobs:
build:
if: github.repository == 'PicnicSupermarket/error-prone-support'
strategy:
matrix:
os: [ ubuntu-22.04 ]

View File

@@ -13,6 +13,7 @@ permissions:
contents: read
jobs:
analyze:
if: github.repository == 'PicnicSupermarket/error-prone-support'
strategy:
matrix:
language: [ java, ruby ]
@@ -39,13 +40,13 @@ jobs:
java-distribution: temurin
maven-version: 3.9.6
- name: Initialize CodeQL
uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
languages: ${{ matrix.language }}
- name: Perform minimal build
if: matrix.language == 'java'
run: mvn -T1C clean package -DskipTests -Dverification.skip
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
category: /language:${{ matrix.language }}

View File

@@ -9,6 +9,7 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
if: github.repository == 'PicnicSupermarket/error-prone-support'
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
@@ -42,12 +43,12 @@ jobs:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0
- uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0
with:
working-directory: ./website
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- name: Generate documentation
run: ./generate-docs.sh
- name: Build website with Jekyll
@@ -82,4 +83,4 @@ jobs:
api.github.com:443
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@decdde0ac072f6dcbe43649d82d9c635fff5b4e4 # v4.0.4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5

View File

@@ -14,6 +14,7 @@ permissions:
contents: read
jobs:
analyze:
if: github.repository == 'PicnicSupermarket/error-prone-support'
permissions:
contents: read
security-events: write
@@ -46,6 +47,6 @@ jobs:
results_format: sarif
publish_results: ${{ github.ref == 'refs/heads/master' }}
- name: Update GitHub's code scanning dashboard
uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
sarif_file: results.sarif

View File

@@ -9,6 +9,7 @@ permissions:
contents: read
jobs:
analyze-pr:
if: github.repository == 'PicnicSupermarket/error-prone-support'
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner

View File

@@ -11,7 +11,7 @@ permissions:
actions: read
jobs:
update-pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'PicnicSupermarket/error-prone-support' }}
permissions:
actions: read
checks: write
@@ -35,7 +35,7 @@ jobs:
java-distribution: temurin
maven-version: 3.9.6
- name: Download Pitest analysis artifact
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: pitest-reports

View File

@@ -9,13 +9,13 @@ name: "Integration tests"
on:
issue_comment:
types: [ created ]
pull_request:
push:
permissions:
contents: read
jobs:
run-integration-tests:
name: On-demand integration test
if: |
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
.DS_Store
.factorypath
.idea
!.idea/icon.svg
.project
.settings
target

65
.idea/icon.svg generated Normal file
View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1259 1199" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-624.154,-988.431)">
<g transform="matrix(1,0,0,1,-70.1122,-35.0561)">
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1227.03,1988.79C1237.78,2070.45 1225.83,2190.1 1192.24,2194.53C1158.65,2198.95 1116.14,2086.46 1105.39,2004.81C1094.64,1923.16 1128.44,1902.11 1153.32,1898.84C1178.18,1895.56 1216.28,1907.14 1227.03,1988.79Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1151.08,1881.86C1134.93,1883.99 1114.77,1892.69 1101.6,1913.17C1088.42,1933.64 1082.71,1963.73 1088.42,2007.04C1094.04,2049.75 1107.59,2099.16 1124.51,2138.68C1132.97,2158.45 1142.15,2175.68 1152.59,2188.86C1163.04,2202.05 1176.31,2213.89 1194.48,2211.5C1212.65,2209.11 1222.39,2194.23 1229.07,2178.8C1235.75,2163.36 1240.15,2144.34 1243.21,2123.05C1249.32,2080.5 1249.63,2029.27 1244,1986.56C1238.3,1943.24 1225,1915.66 1206.98,1899.29C1188.95,1882.93 1167.22,1879.74 1151.08,1881.86ZM1155.55,1915.81C1164.27,1914.66 1174.03,1915.62 1183.96,1924.64C1193.89,1933.66 1205.01,1952.69 1210.06,1991.03C1215.18,2029.97 1214.89,2079.4 1209.32,2118.19C1206.53,2137.58 1202.32,2154.4 1197.65,2165.2C1194.14,2173.29 1190.82,2176.3 1189.96,2177.22C1188.89,2176.55 1184.91,2174.51 1179.43,2167.6C1172.12,2158.38 1163.7,2143.22 1155.99,2125.21C1140.57,2089.18 1127.49,2041.51 1122.36,2002.57C1117.32,1964.24 1123.13,1942.97 1130.39,1931.69C1137.65,1920.42 1146.82,1916.96 1155.55,1915.81Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1516.33,1963.1C1466.19,1897.75 1427.4,1906.77 1407.5,1922.05C1387.59,1937.32 1368.84,1972.45 1418.98,2037.8C1431.75,2054.44 1447.26,2071.84 1463.69,2088.19C1495.18,2119.52 1534.33,2139.39 1582.98,2126.14C1606.4,2119.76 1622.19,2110.46 1623.75,2098.64C1625.79,2083.16 1603,2065.78 1569.69,2050.47C1554.75,2019.83 1535.59,1988.2 1516.33,1963.1Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1397.07,1908.46C1409.99,1898.55 1430.41,1890.44 1454.2,1895.61C1478,1900.77 1503.31,1918 1529.91,1952.67C1548.92,1977.44 1567.3,2007.65 1582.28,2037.56C1597.47,2044.87 1610.74,2052.64 1621.09,2061.47C1632.68,2071.35 1642.93,2084.12 1640.73,2100.88C1639.05,2113.64 1630.31,2122.66 1620.9,2128.78C1611.49,2134.9 1600.29,2139.17 1587.48,2142.66C1532.39,2157.66 1485.57,2134.11 1451.61,2100.32C1434.7,2083.49 1418.73,2065.6 1405.39,2048.22C1378.79,2013.56 1368.69,1984.64 1369.86,1960.32C1371.04,1936 1384.15,1918.38 1397.07,1908.46ZM1417.92,1935.63C1410.94,1940.99 1404.71,1948.57 1404.07,1961.97C1403.43,1975.37 1409.02,1996.69 1432.56,2027.38C1444.76,2043.27 1459.82,2060.18 1475.77,2076.05C1504.8,2104.93 1536.26,2121.12 1578.48,2109.62C1589.1,2106.73 1597.5,2103.16 1602.23,2100.08C1605.14,2098.18 1606.16,2096.97 1606.54,2096.46C1606.07,2095.66 1604.57,2092.39 1598.86,2087.52C1591.24,2081.02 1578.31,2073.28 1562.54,2066.03L1556.98,2063.47L1554.29,2057.97C1539.86,2028.35 1521.12,1997.46 1502.75,1973.52C1479.2,1942.84 1460.05,1931.91 1446.94,1929.07C1433.84,1926.23 1424.9,1930.27 1417.92,1935.63Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M917.121,1633.14C845.801,1674.32 730.68,1709.07 713.738,1679.73C696.797,1650.39 784.453,1568.07 855.777,1526.89C927.102,1485.71 959.48,1508.89 972.02,1530.62C984.562,1552.34 988.445,1591.97 917.121,1633.14Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M986.848,1522.06C978.707,1507.95 962.949,1492.66 938.992,1488.32C915.031,1483.98 885.055,1490.22 847.219,1512.07C809.906,1533.61 769.453,1565.03 739.41,1595.79C724.391,1611.17 711.977,1626.24 703.797,1640.94C695.617,1655.64 689.746,1672.42 698.914,1688.29C708.078,1704.17 725.547,1707.48 742.363,1707.74C759.184,1708.01 778.445,1704.79 799.273,1699.47C840.934,1688.83 888.375,1669.51 925.684,1647.97C963.52,1626.12 983.91,1603.29 992.137,1580.37C1000.36,1557.45 994.988,1536.16 986.848,1522.06ZM957.195,1539.18C961.594,1546.79 964.438,1556.18 959.906,1568.8C955.379,1581.43 942.047,1598.98 908.562,1618.32C874.551,1637.96 828.77,1656.6 790.801,1666.3C771.816,1671.14 754.664,1673.69 742.902,1673.5C734.082,1673.37 730.035,1671.45 728.859,1671C729.062,1669.76 729.426,1665.3 733.715,1657.59C739.434,1647.31 750.215,1633.73 763.906,1619.72C791.285,1591.68 830.324,1561.36 864.336,1541.72C897.824,1522.38 919.695,1519.62 932.895,1522.01C946.09,1524.4 952.797,1531.56 957.195,1539.18Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1791.57,1526.89C1862.89,1568.07 1950.54,1650.39 1933.61,1679.74C1916.66,1709.08 1801.54,1674.33 1730.22,1633.15C1658.9,1591.97 1662.78,1552.34 1675.32,1530.62C1687.86,1508.89 1720.24,1485.72 1791.57,1526.89Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1660.5,1522.06C1652.35,1536.16 1646.98,1557.45 1655.21,1580.37C1663.43,1603.29 1683.82,1626.13 1721.66,1647.97C1758.97,1669.52 1806.41,1688.84 1848.07,1699.48C1868.9,1704.79 1888.16,1708.01 1904.98,1707.75C1921.79,1707.48 1939.27,1704.17 1948.43,1688.3C1957.59,1672.42 1951.73,1655.64 1943.55,1640.94C1935.37,1626.25 1922.95,1611.17 1907.93,1595.79C1877.89,1565.04 1837.43,1533.61 1800.12,1512.07C1762.29,1490.22 1732.31,1483.98 1708.35,1488.32C1684.39,1492.66 1668.64,1507.95 1660.5,1522.06ZM1690.15,1539.18C1694.55,1531.56 1701.25,1524.4 1714.45,1522.02C1727.64,1519.62 1749.52,1522.39 1783,1541.72C1817.02,1561.36 1856.06,1591.68 1883.44,1619.72C1897.12,1633.73 1907.91,1647.32 1913.63,1657.59C1917.92,1665.3 1918.28,1669.77 1918.48,1671.01C1917.31,1671.45 1913.26,1673.37 1904.44,1673.51C1892.68,1673.69 1875.52,1671.15 1856.54,1666.3C1818.57,1656.61 1772.79,1637.96 1738.78,1618.32C1705.29,1598.99 1691.97,1581.43 1687.43,1568.81C1682.91,1556.18 1685.75,1546.8 1690.15,1539.18Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1322.81,1013.67C1058.16,1014.17 843.293,1229.45 843.297,1494.11L843.223,1862.19C843.223,1955.32 919.055,2031.16 1012.19,2031.16C1054.55,2031.16 1093.39,2015.51 1123.09,1989.7C1169.54,2049.44 1242.17,2087.79 1323.7,2087.79C1405.22,2087.79 1477.84,2049.45 1524.28,1989.73C1553.98,2015.52 1592.81,2031.16 1635.15,2031.16C1728.29,2031.16 1804.12,1955.32 1804.12,1862.19L1804.12,1494.1C1804.12,1229.09 1588.7,1013.67 1323.69,1013.67L1322.84,1013.67L1322.81,1013.67ZM1322.92,1068.46L1323.69,1068.46C1559.09,1068.46 1749.33,1258.7 1749.33,1494.11L1749.33,1862.19C1749.33,1925.92 1698.88,1976.37 1635.15,1976.37C1596.91,1976.37 1563.67,1958.03 1542.94,1929.68L1517.91,1895.48L1497,1932.34C1462.85,1992.53 1398.48,2033 1323.7,2033C1248.9,2033 1184.52,1992.51 1150.38,1932.3L1129.45,1895.41L1104.43,1929.65C1083.69,1958.02 1050.44,1976.37 1012.19,1976.37C948.461,1976.37 898.016,1925.93 898.012,1862.2L898.086,1494.11C898.086,1259.03 1087.84,1068.92 1322.92,1068.47L1322.92,1068.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1322.86,1041.07C1072.99,1041.54 870.684,1244.23 870.688,1494.11L870.648,1862.19C870.648,1940.62 933.789,2003.77 1012.22,2003.77C1073.65,2003.77 1125.69,1965.03 1145.37,1910.56C1201.73,1934.69 1262.41,1947.14 1323.72,1947.14C1385.02,1947.14 1445.69,1934.69 1502.04,1910.57C1521.72,1965.04 1573.76,2003.77 1635.19,2003.77C1713.62,2003.77 1776.76,1940.62 1776.76,1862.19L1776.76,1494.11C1776.76,1243.9 1573.93,1041.07 1323.72,1041.07L1322.86,1041.07Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1323.7,1494.1C1449.2,1494.1 1550.24,1595.14 1550.24,1720.64L1550.24,1833.86C1550.24,1959.36 1449.2,2060.4 1323.7,2060.4C1198.2,2060.4 1097.16,1959.36 1097.16,1833.86L1097.16,1720.64C1097.16,1595.14 1198.2,1494.1 1323.7,1494.1Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1322.86,1041.07C1262.14,1041.18 1204.25,1053.27 1151.36,1075.05C1316.07,1142.87 1432.03,1304.93 1432.03,1494.11L1432.03,1811.95C1432.03,2003.63 1209.62,2024.85 1126.54,1945.82C1177.52,2034.1 1254.55,2060.4 1323.7,2060.4C1408.4,2060.4 1481.95,2014.37 1520.82,1945.86C1546.53,1981.01 1588.08,2003.77 1635.15,2003.77C1713.58,2003.77 1776.72,1940.62 1776.72,1862.19L1776.72,1494.11C1776.72,1243.9 1573.89,1041.07 1323.68,1041.07L1322.86,1041.07Z" style="fill:rgb(219,220,211);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1322.86,1041.07C1304.33,1041.1 1286.06,1042.28 1268.11,1044.48C1492.04,1071.93 1665.46,1262.75 1665.46,1494.11L1665.46,1862.19C1665.46,1920.85 1630.14,1970.94 1579.54,1992.48C1596.59,1999.74 1615.38,2003.77 1635.15,2003.77C1713.58,2003.77 1776.72,1940.62 1776.72,1862.19L1776.72,1494.11C1776.72,1243.9 1573.89,1041.07 1323.68,1041.07L1322.86,1041.07Z" style="fill:rgb(189,191,175);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1322.85,1034.22C1069.29,1034.69 863.84,1240.54 863.84,1494.11L863.766,1862.19C863.766,1944.3 930.078,2010.61 1012.19,2010.61C1057.88,2010.61 1098.53,1989.94 1125.71,1957.58C1166.88,2023.5 1240.02,2067.25 1323.7,2067.25C1407.36,2067.25 1480.48,2023.52 1521.66,1957.62C1548.84,1989.96 1589.47,2010.61 1635.15,2010.61C1717.25,2010.61 1783.57,1944.3 1783.57,1862.19L1783.57,1494.11C1783.57,1240.2 1577.59,1034.21 1323.68,1034.22L1322.85,1034.22ZM1322.86,1047.92L1323.68,1047.92C1570.19,1047.92 1769.87,1247.6 1769.87,1494.11L1769.87,1862.19C1769.87,1936.95 1709.91,1996.92 1635.15,1996.92C1590.29,1996.92 1550.82,1975.26 1526.36,1941.82L1520.1,1933.27L1514.87,1942.48C1477.18,2008.91 1405.92,2053.55 1323.7,2053.55C1241.46,2053.55 1170.19,2008.89 1132.5,1942.44L1127.27,1933.21L1121.02,1941.77C1096.56,1975.24 1057.07,1996.92 1012.19,1996.92C937.43,1996.92 877.469,1936.95 877.465,1862.19L877.539,1494.11C877.539,1247.94 1076.7,1048.39 1322.86,1047.92Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1576.29,1470.72C1576.29,1481.36 1568.75,1491.56 1555.31,1499.08C1541.88,1506.6 1523.67,1510.83 1504.68,1510.83C1465.12,1510.83 1433.06,1492.87 1433.06,1470.72C1433.06,1448.57 1465.12,1430.62 1504.68,1430.62C1523.67,1430.62 1541.88,1434.84 1555.31,1442.36C1568.75,1449.89 1576.29,1460.09 1576.29,1470.72Z" style="fill:rgb(255,155,173);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1214.28,1470.72C1214.28,1481.36 1206.73,1491.56 1193.31,1499.08C1179.87,1506.6 1161.66,1510.83 1142.66,1510.83C1103.11,1510.83 1071.05,1492.87 1071.05,1470.72C1071.05,1448.57 1103.11,1430.62 1142.66,1430.62C1161.66,1430.62 1179.87,1434.84 1193.31,1442.36C1206.73,1449.89 1214.28,1460.09 1214.28,1470.72Z" style="fill:rgb(255,155,173);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1104.39,1401.46C1104.39,1375.15 1118.45,1350.79 1141.24,1337.63C1164.03,1324.48 1192.16,1324.48 1214.95,1337.63C1237.74,1350.79 1251.81,1375.15 1251.81,1401.46L1224.41,1401.46C1224.41,1384.9 1215.6,1369.64 1201.25,1361.36C1186.9,1353.07 1169.29,1353.07 1154.94,1361.36C1140.59,1369.64 1131.78,1384.9 1131.78,1401.46L1104.39,1401.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1395.54,1401.46C1395.54,1375.15 1409.61,1350.79 1432.39,1337.63C1455.18,1324.48 1483.32,1324.48 1506.11,1337.63C1528.89,1350.79 1542.96,1375.15 1542.96,1401.46L1515.56,1401.46C1515.56,1384.9 1506.75,1369.64 1492.41,1361.36C1478.06,1353.07 1460.44,1353.07 1446.09,1361.36C1431.74,1369.64 1422.93,1384.9 1422.93,1401.46L1395.54,1401.46Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,0,9.81959)">
<path d="M1256.81,1448.61C1256.81,1472.48 1269.56,1494.57 1290.24,1506.51C1310.92,1518.45 1336.42,1518.45 1357.1,1506.51C1377.78,1494.57 1390.53,1472.48 1390.53,1448.61L1376.83,1448.61C1376.83,1467.61 1366.71,1485.15 1350.25,1494.65C1333.79,1504.15 1313.55,1504.15 1297.09,1494.65C1280.63,1485.15 1270.51,1467.61 1270.51,1448.61L1256.81,1448.61Z" style="fill:rgb(26,26,26);fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>documentation-support</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>

View File

@@ -0,0 +1,135 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static java.util.stream.Collectors.joining;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.Optional;
import java.util.function.Supplier;
import tech.picnic.errorprone.utils.SourceCode;
/**
* A {@link BugChecker} that flags arguments to {@link Optional#orElse(Object)} that should be
* deferred using {@link Optional#orElseGet(Supplier)}.
*
* <p>The suggested fix assumes that the argument to {@code orElse} does not have side effects. If
* it does, the suggested fix changes the program's semantics. Such fragile code must instead be
* refactored such that the side-effectful code does not appear accidental.
*/
// XXX: Consider also implementing the inverse, in which `.orElseGet(() -> someConstant)` is
// flagged.
// XXX: Once the `MethodReferenceUsageCheck` becomes generally usable, consider leaving the method
// reference cleanup to that check, and express the remainder of the logic in this class using a
// Refaster template, i.c.w. a `@Matches` constraint that implements the `requiresComputation`
// logic.
@AutoService(BugChecker.class)
@BugPattern(
summary =
"""
Prefer `Optional#orElseGet` over `Optional#orElse` if the fallback requires additional \
computation""",
linkType = NONE,
severity = WARNING,
tags = PERFORMANCE)
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> OPTIONAL_OR_ELSE_METHOD =
instanceMethod().onExactClass(Optional.class.getCanonicalName()).namedAnyOf("orElse");
// XXX: Also exclude invocations of `@Placeholder`-annotated methods.
private static final Matcher<ExpressionTree> REFASTER_METHOD =
staticMethod().onClass(Refaster.class.getCanonicalName());
/** Instantiates a new {@link OptionalOrElse} instance. */
public OptionalOrElse() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!OPTIONAL_OR_ELSE_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
return Description.NO_MATCH;
}
/*
* We have a match. Construct the method reference or lambda expression to be passed to the
* replacement `#orElseGet` invocation.
*/
String newArgument =
tryMethodReferenceConversion(argument, state)
.orElseGet(() -> "() -> " + SourceCode.treeToString(argument, state));
/* Construct the suggested fix, replacing the method invocation and its argument. */
SuggestedFix fix =
SuggestedFix.builder()
.merge(SuggestedFixes.renameMethodInvocation(tree, "orElseGet", state))
.replace(argument, newArgument)
.build();
return describeMatch(tree, fix);
}
/**
* Tells whether the given expression contains anything other than a literal or a (possibly
* dereferenced) variable or constant.
*/
private static boolean requiresComputation(ExpressionTree tree) {
return !(tree instanceof IdentifierTree
|| tree instanceof LiteralTree
|| (tree instanceof MemberSelectTree memberSelect
&& !requiresComputation(memberSelect.getExpression()))
|| ASTHelpers.constValue(tree) != null);
}
/** Returns the nullary method reference matching the given expression, if any. */
private static Optional<String> tryMethodReferenceConversion(
ExpressionTree tree, VisitorState state) {
if (!(tree instanceof MethodInvocationTree methodInvocation)) {
return Optional.empty();
}
if (!methodInvocation.getArguments().isEmpty()) {
return Optional.empty();
}
if (!(methodInvocation.getMethodSelect() instanceof MemberSelectTree memberSelect)) {
return Optional.empty();
}
if (requiresComputation(memberSelect.getExpression())) {
return Optional.empty();
}
return Optional.of(
SourceCode.treeToString(memberSelect.getExpression(), state)
+ "::"
+ (methodInvocation.getTypeArguments().isEmpty()
? ""
: methodInvocation.getTypeArguments().stream()
.map(arg -> SourceCode.treeToString(arg, state))
.collect(joining(",", "<", ">")))
+ memberSelect.getIdentifier());
}
}

View File

@@ -39,7 +39,7 @@ final class CollectionRules {
"java:S1155" /* This violation will be rewritten. */,
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
"OptionalFirstCollectionElement" /* This is a more specific template. */,
"StreamIsEmpty" /* This is a more specific template. */,
"StreamFindAnyIsEmpty" /* This is a more specific template. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
boolean before(Collection<T> collection) {

View File

@@ -16,8 +16,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
@@ -92,6 +95,24 @@ final class ComparatorRules {
}
}
/** Don't explicitly compare enums by their ordinal. */
abstract static class ComparingEnum<E extends Enum<E>, T> {
@Placeholder(allowsIdentity = true)
abstract E toEnumFunction(@MayOptionallyUse T value);
@BeforeTemplate
@SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */)
Comparator<T> before() {
return comparingInt(v -> toEnumFunction(v).ordinal());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return comparing(v -> toEnumFunction(v));
}
}
/** Don't explicitly create {@link Comparator}s unnecessarily. */
static final class ThenComparing<S, T extends Comparable<? super T>> {
@BeforeTemplate
@@ -419,4 +440,34 @@ final class ComparatorRules {
return maxBy(naturalOrder());
}
}
/** Don't explicitly compare enums by their ordinal. */
static final class IsLessThan<E extends Enum<E>> {
@BeforeTemplate
@SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */)
boolean before(E value1, E value2) {
return value1.ordinal() < value2.ordinal();
}
@AfterTemplate
@AlsoNegation
boolean after(E value1, E value2) {
return value1.compareTo(value2) < 0;
}
}
/** Don't explicitly compare enums by their ordinal. */
static final class IsLessThanOrEqualTo<E extends Enum<E>> {
@BeforeTemplate
@SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */)
boolean before(E value1, E value2) {
return value1.ordinal() <= value2.ordinal();
}
@AfterTemplate
@AlsoNegation
boolean after(E value1, E value2) {
return value1.compareTo(value2) <= 0;
}
}
}

View File

@@ -30,8 +30,9 @@ final class EqualityRules {
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
@BeforeTemplate
@SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */)
boolean before(T a, T b) {
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
return Refaster.anyOf(a.equals(b), Objects.equals(a, b), a.ordinal() == b.ordinal());
}
@AfterTemplate

View File

@@ -360,7 +360,7 @@ final class OptionalRules {
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
static final class OptionalOrOtherOptional<T> {
@BeforeTemplate
@SuppressWarnings("NestedOptionals" /* Auto-fix for the `NestedOptionals` check. */)
@SuppressWarnings("NestedOptionals")
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
// XXX: Note that rewriting the first and third variant will change the code's behavior if
// `optional2` has side-effects.
@@ -386,9 +386,13 @@ final class OptionalRules {
*/
static final class OptionalIdentity<T> {
@BeforeTemplate
@SuppressWarnings("NestedOptionals")
Optional<T> before(Optional<T> optional, Comparator<? super T> comparator) {
return Refaster.anyOf(
optional.or(Refaster.anyOf(() -> Optional.empty(), Optional::empty)),
optional
.map(Optional::of)
.orElseGet(Refaster.anyOf(() -> Optional.empty(), Optional::empty)),
optional.stream().findFirst(),
optional.stream().findAny(),
optional.stream().min(comparator),
@@ -442,9 +446,7 @@ final class OptionalRules {
static final class OptionalStream<T> {
@BeforeTemplate
Stream<T> before(Optional<T> optional) {
return Refaster.anyOf(
optional.map(Stream::of).orElse(Stream.empty()),
optional.map(Stream::of).orElseGet(Stream::empty));
return optional.map(Stream::of).orElseGet(Stream::empty);
}
@AfterTemplate

View File

@@ -25,6 +25,7 @@ import com.google.common.collect.Streams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
@@ -256,7 +257,7 @@ final class StreamRules {
// XXX: This rule assumes that any matched `Collector` does not perform any filtering.
// (Perhaps we could add a `@Matches` guard that validates that the collector expression does not
// contain a `Collectors#filtering` call. That'd still not be 100% accurate, though.)
static final class StreamIsEmpty<T, K, V, C extends Collection<K>, M extends Map<K, V>> {
static final class StreamFindAnyIsEmpty<T, K, V, C extends Collection<K>, M extends Map<K, V>> {
@BeforeTemplate
boolean before(Stream<T> stream, Collector<? super T, ?, ? extends C> collector) {
return Refaster.anyOf(
@@ -274,20 +275,20 @@ final class StreamRules {
}
@AfterTemplate
@AlsoNegation
boolean after(Stream<T> stream) {
return stream.findAny().isEmpty();
}
}
/** In order to test whether a stream has any element, simply try to find one. */
static final class StreamIsNotEmpty<T> {
/**
* Prefer {@link Stream#findAny()} over {@link Stream#findFirst()} if one only cares whether the
* stream is nonempty.
*/
static final class StreamFindAnyIsPresent<T> {
@BeforeTemplate
boolean before(Stream<T> stream) {
return Refaster.anyOf(
stream.count() != 0,
stream.count() > 0,
stream.count() >= 1,
stream.findFirst().isPresent());
return stream.findFirst().isPresent();
}
@AfterTemplate

View File

@@ -0,0 +1,135 @@
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 OptionalOrElseTest {
@Test
void identification() {
CompilationTestHelper.newInstance(OptionalOrElse.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",
"import java.util.Optional;",
"",
"class A {",
" private final Optional<Object> optional = Optional.empty();",
" private final String string = optional.toString();",
"",
" void m() {",
" Optional.empty().orElse(null);",
" optional.orElse(null);",
" optional.orElse(\"constant\");",
" optional.orElse(\"constant\" + 0);",
" optional.orElse(Boolean.TRUE);",
" optional.orElse(string);",
" optional.orElse(this.string);",
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
"",
" // BUG: Diagnostic contains:",
" Optional.empty().orElse(string + \"constant\");",
" // BUG: Diagnostic contains:",
" optional.orElse(string + \"constant\");",
" // BUG: Diagnostic contains:",
" optional.orElse(\"constant\".toString());",
" // BUG: Diagnostic contains:",
" optional.orElse(string.toString());",
" // BUG: Diagnostic contains:",
" optional.orElse(this.string.toString());",
" // BUG: Diagnostic contains:",
" optional.orElse(String.valueOf(42));",
" // BUG: Diagnostic contains:",
" optional.orElse(string.toString().length());",
" // BUG: Diagnostic contains:",
" optional.orElse(\"constant\".equals(string));",
" // BUG: Diagnostic contains:",
" optional.orElse(string.equals(string));",
" // BUG: Diagnostic contains:",
" optional.orElse(this.string.equals(string));",
" // BUG: Diagnostic contains:",
" optional.orElse(foo());",
" // BUG: Diagnostic contains:",
" optional.orElse(this.foo());",
" // BUG: Diagnostic contains:",
" optional.orElse(new Object() {});",
" // BUG: Diagnostic contains:",
" optional.orElse(new int[0].length);",
" }",
"",
" private <T> T foo() {",
" return null;",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElse.class, getClass())
.addInputLines(
"A.java",
"import java.util.Optional;",
"",
"class A {",
" private final Optional<Object> optional = Optional.empty();",
" private final String string = optional.toString();",
"",
" void m() {",
" optional.orElse(string + \"constant\");",
" optional.orElse(\"constant\".toString());",
" optional.orElse(string.toString());",
" optional.orElse(this.string.toString());",
" optional.orElse(String.valueOf(42));",
" optional.orElse(string.toString().length());",
" optional.orElse(string.equals(string));",
" optional.orElse(foo());",
" optional.orElse(this.<Number>foo());",
" optional.orElse(this.<String, Integer>bar());",
" optional.orElse(new Object() {});",
" optional.orElse(new int[0].length);",
" }",
"",
" private <T> T foo() {",
" return null;",
" }",
"",
" private <S, T> T bar() {",
" return null;",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Optional;",
"",
"class A {",
" private final Optional<Object> optional = Optional.empty();",
" private final String string = optional.toString();",
"",
" void m() {",
" optional.orElseGet(() -> string + \"constant\");",
" optional.orElseGet(\"constant\"::toString);",
" optional.orElseGet(string::toString);",
" optional.orElseGet(this.string::toString);",
" optional.orElseGet(() -> String.valueOf(42));",
" optional.orElseGet(() -> string.toString().length());",
" optional.orElseGet(() -> string.equals(string));",
" optional.orElseGet(() -> foo());",
" optional.orElseGet(this::<Number>foo);",
" optional.orElseGet(this::<String, Integer>bar);",
" optional.orElseGet(() -> new Object() {});",
" optional.orElseGet(() -> new int[0].length);",
" }",
"",
" private <T> T foo() {",
" return null;",
" }",
"",
" private <S, T> T bar() {",
" return null;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -19,7 +19,6 @@ final class Slf4jLogStatementTest {
"class A {",
" private static final String FMT0 = \"format-string-without-placeholders\";",
" private static final String FMT1 = \"format-string-with-{}-placeholder\";",
" private static final String FMT2 = \"format-string-with-{}-{}-placeholders\";",
" private static final String FMT_ERR = \"format-string-with-%s-placeholder\";",
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
"",

View File

@@ -9,6 +9,7 @@ import static java.util.stream.Collectors.minBy;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@@ -54,6 +55,10 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.comparing(s -> "foo", Comparator.comparingInt(String::length)));
}
Comparator<String> testComparingEnum() {
return Comparator.comparingInt(s -> RoundingMode.valueOf(s).ordinal());
}
Comparator<String> testThenComparing() {
return Comparator.<String>naturalOrder().thenComparing(Comparator.comparing(String::isEmpty));
}
@@ -173,4 +178,16 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Collector<Integer, ?, Optional<Integer>> testMaxByNaturalOrder() {
return minBy(reverseOrder());
}
ImmutableSet<Boolean> testIsLessThan() {
return ImmutableSet.of(
RoundingMode.UP.ordinal() < RoundingMode.DOWN.ordinal(),
RoundingMode.UP.ordinal() >= RoundingMode.DOWN.ordinal());
}
ImmutableSet<Boolean> testIsLessThanOrEqualTo() {
return ImmutableSet.of(
RoundingMode.UP.ordinal() <= RoundingMode.DOWN.ordinal(),
RoundingMode.UP.ordinal() > RoundingMode.DOWN.ordinal());
}
}

View File

@@ -1,5 +1,6 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
@@ -9,6 +10,7 @@ import static java.util.stream.Collectors.minBy;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
@@ -52,6 +54,10 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.comparing(s -> "foo", Comparator.comparingInt(String::length)));
}
Comparator<String> testComparingEnum() {
return comparing(s -> RoundingMode.valueOf(s));
}
Comparator<String> testThenComparing() {
return Comparator.<String>naturalOrder().thenComparing(String::isEmpty);
}
@@ -163,4 +169,16 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Collector<Integer, ?, Optional<Integer>> testMaxByNaturalOrder() {
return maxBy(naturalOrder());
}
ImmutableSet<Boolean> testIsLessThan() {
return ImmutableSet.of(
RoundingMode.UP.compareTo(RoundingMode.DOWN) < 0,
RoundingMode.UP.compareTo(RoundingMode.DOWN) >= 0);
}
ImmutableSet<Boolean> testIsLessThanOrEqualTo() {
return ImmutableSet.of(
RoundingMode.UP.compareTo(RoundingMode.DOWN) <= 0,
RoundingMode.UP.compareTo(RoundingMode.DOWN) > 0);
}
}

View File

@@ -21,8 +21,10 @@ final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
RoundingMode.UP.equals(RoundingMode.DOWN),
Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
RoundingMode.UP.ordinal() == RoundingMode.DOWN.ordinal(),
!RoundingMode.UP.equals(RoundingMode.DOWN),
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
RoundingMode.UP.ordinal() != RoundingMode.DOWN.ordinal());
}
boolean testEqualsPredicate() {

View File

@@ -21,6 +21,8 @@ final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN);
}

View File

@@ -120,10 +120,12 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Optional.of("foo").or(() -> Optional.empty()),
Optional.of("bar").or(Optional::empty),
Optional.of("baz").stream().findFirst(),
Optional.of("qux").stream().findAny(),
Optional.of("quux").stream().min(String::compareTo),
Optional.of("quuz").stream().max(String::compareTo));
Optional.of("baz").map(Optional::of).orElseGet(() -> Optional.empty()),
Optional.of("qux").map(Optional::of).orElseGet(Optional::empty),
Optional.of("quux").stream().findFirst(),
Optional.of("quuz").stream().findAny(),
Optional.of("corge").stream().min(String::compareTo),
Optional.of("grault").stream().max(String::compareTo));
}
ImmutableSet<Optional<String>> testOptionalFilter() {
@@ -136,9 +138,7 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of(1).stream().map(String::valueOf).findAny();
}
ImmutableSet<Stream<String>> testOptionalStream() {
return ImmutableSet.of(
Optional.of("foo").map(Stream::of).orElse(Stream.empty()),
Optional.of("bar").map(Stream::of).orElseGet(Stream::empty));
Stream<String> testOptionalStream() {
return Optional.of("foo").map(Stream::of).orElseGet(Stream::empty);
}
}

View File

@@ -120,7 +120,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
Optional.of("baz"),
Optional.of("qux"),
Optional.of("quux"),
Optional.of("quuz"));
Optional.of("quuz"),
Optional.of("corge"),
Optional.of("grault"));
}
ImmutableSet<Optional<String>> testOptionalFilter() {
@@ -132,7 +134,7 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of(1).map(String::valueOf);
}
ImmutableSet<Stream<String>> testOptionalStream() {
return ImmutableSet.of(Optional.of("foo").stream(), Optional.of("bar").stream());
Stream<String> testOptionalStream() {
return Optional.of("foo").stream();
}
}

View File

@@ -120,7 +120,7 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
Stream.of("bar").map(String::length).findFirst());
}
ImmutableSet<Boolean> testStreamIsEmpty() {
ImmutableSet<Boolean> testStreamFindAnyIsEmpty() {
return ImmutableSet.of(
Stream.of(1).count() == 0,
Stream.of(2).count() <= 0,
@@ -131,15 +131,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
Stream.of(7).collect(collectingAndThen(toImmutableList(), ImmutableList::isEmpty)),
Stream.of(8).collect(collectingAndThen(toImmutableMap(k -> k, v -> v), Map::isEmpty)),
Stream.of(9)
.collect(collectingAndThen(toImmutableMap(k -> k, v -> v), ImmutableMap::isEmpty)));
.collect(collectingAndThen(toImmutableMap(k -> k, v -> v), ImmutableMap::isEmpty)),
Stream.of(10).count() != 0,
Stream.of(11).count() > 0,
Stream.of(12).count() >= 1);
}
ImmutableSet<Boolean> testStreamIsNotEmpty() {
return ImmutableSet.of(
Stream.of(1).count() != 0,
Stream.of(2).count() > 0,
Stream.of(3).count() >= 1,
Stream.of(4).findFirst().isPresent());
boolean testStreamFindAnyIsPresent() {
return Stream.of(1).findFirst().isPresent();
}
ImmutableSet<Optional<String>> testStreamMin() {

View File

@@ -121,7 +121,7 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
Stream.of("bar").findFirst().map(String::length));
}
ImmutableSet<Boolean> testStreamIsEmpty() {
ImmutableSet<Boolean> testStreamFindAnyIsEmpty() {
return ImmutableSet.of(
Stream.of(1).findAny().isEmpty(),
Stream.of(2).findAny().isEmpty(),
@@ -131,15 +131,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
Stream.of(6).findAny().isEmpty(),
Stream.of(7).findAny().isEmpty(),
Stream.of(8).findAny().isEmpty(),
Stream.of(9).findAny().isEmpty());
Stream.of(9).findAny().isEmpty(),
!Stream.of(10).findAny().isEmpty(),
!Stream.of(11).findAny().isEmpty(),
!Stream.of(12).findAny().isEmpty());
}
ImmutableSet<Boolean> testStreamIsNotEmpty() {
return ImmutableSet.of(
Stream.of(1).findAny().isPresent(),
Stream.of(2).findAny().isPresent(),
Stream.of(3).findAny().isPresent(),
Stream.of(4).findAny().isPresent());
boolean testStreamFindAnyIsPresent() {
return Stream.of(1).findAny().isPresent();
}
ImmutableSet<Optional<String>> testStreamMin() {

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>error-prone-experimental</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>error-prone-guidelines</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>error-prone-utils</artifactId>

View File

@@ -53686,6 +53686,15 @@
final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
final Path path = Paths.get(XdocUtil.DIRECTORY_PATH + "/config.xml");
@@ -1081,7 +1083,7 @@ public class XdocsPagesTest {
Optional.ofNullable(field)
.map(nonNullField -> nonNullField.getAnnotation(XdocsPropertyType.class))
.map(propertyType -> propertyType.value().getDescription())
- .orElse(fieldClass.getSimpleName());
+ .orElseGet(fieldClass::getSimpleName);
final String expectedValue =
getModulePropertyExpectedValue(sectionName, propertyName, field, fieldClass, instance);
@@ -1364,7 +1366,7 @@ public class XdocsPagesTest {
final Object[] array = (Object[]) value;
valuesStream = Arrays.stream(array);

View File

@@ -66,21 +66,23 @@ error_prone_patch_flags="${error_prone_shared_flags} -XepPatchLocation:IN_PLACE
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-not -path "*/error-prone-contrib/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP '[^.]+$' \
| paste -s -d ',' -
)"
) -XepOpt:Refaster:NamePattern=.*Workshop.*"
error_prone_validation_flags="${error_prone_shared_flags} -XepDisableAllChecks $(
find "${error_prone_support_root}" \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-not -path "*/error-prone-contrib/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP '[^.]+$' \
| xargs -0 "${grep_command}" -hoP "[^.]+$' \
| "${sed_command}" -r 's,(.*),-Xep:\1:WARN,' \
| paste -s -d ' ' -
)"
) -XepOpt:Refaster:NamePattern=.*Workshop.*"
echo "Shared build flags: ${shared_build_flags}"
echo "Error Prone patch flags: ${error_prone_patch_flags}"
@@ -168,6 +170,7 @@ mvn ${shared_build_flags} \
-Dtest='
!MetadataGeneratorUtilTest#metadataFilesGenerationAllFiles,
!XdocsJavaDocsTest#allCheckSectionJavaDocs' \
-Dstyle.color=always \
| tee "${validation_build_log}" \
|| failure=1
@@ -191,12 +194,15 @@ else
# XXX: This "diff of diffs" also contains vacuous sections, introduced due to
# line offset differences. Try to omit those from the final output.
if ! diff -u "${expected_changes}" "${actual_changes}"; then
echo 'There are unexpected changes. Inspect the preceding output for details.'
echo 'There are unexpected changes.'
echo "Inspect the changes here: ${report_directory}/${test_name}-diff-of-diffs-changes.patch"
diff -u "${expected_changes}" "${actual_changes}" > "${report_directory}/${test_name}-diff-of-diffs-changes.patch"
failure=1
fi
echo 'Inspecting emitted warnings...'
if ! diff -u "${expected_warnings}" "${actual_warnings}"; then
echo 'Diagnostics output changed. Inspect the preceding output for details.'
echo 'Diagnostics output changed.'
diff -u "${expected_warnings}" "${actual_warnings}" > "${report_directory}/${test_name}-diff-of-diffs-warnings.patch"
failure=1
fi
fi

70
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.1</version>
<version>0.16.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -48,11 +48,12 @@
<module>refaster-runner</module>
<module>refaster-support</module>
<module>refaster-test-support</module>
<module>workshop</module>
</modules>
<scm child.scm.developerConnection.inherit.append.path="false" child.scm.url.inherit.append.path="false">
<developerConnection>scm:git:git@github.com:PicnicSupermarket/error-prone-support.git</developerConnection>
<tag>v0.16.1</tag>
<tag>HEAD</tag>
<url>https://github.com/PicnicSupermarket/error-prone-support</url>
</scm>
<issueManagement>
@@ -148,7 +149,7 @@
<groupId.error-prone>com.google.errorprone</groupId.error-prone>
<!-- The build timestamp is derived from the most recent commit
timestamp in support of reproducible builds. -->
<project.build.outputTimestamp>2024-03-15T12:02:48Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2024-03-15T12:04:44Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Glob pattern identifying Refaster rule definition files. These
Java classes don't contain "regular" code, and thus require special
@@ -208,16 +209,16 @@
<version.auto-value>1.10.4</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-2</version.error-prone-fork>
<version.error-prone-orig>2.25.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.22</version.error-prone-slf4j>
<version.error-prone-orig>2.26.1</version.error-prone-orig>
<version.error-prone-slf4j>0.1.23</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>17</version.jdk>
<version.maven>3.9.5</version.maven>
<version.mockito>5.11.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.10.24</version.nullaway>
<version.nullaway>0.10.25</version.nullaway>
<version.pitest-git>1.1.4</version.pitest-git>
<version.rewrite-templating>1.6.2</version.rewrite-templating>
<version.rewrite-templating>1.6.3</version.rewrite-templating>
<version.surefire>3.2.3</version.surefire>
</properties>
@@ -296,7 +297,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.16.2</version>
<version>2.17.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -328,7 +329,7 @@
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
<version>1.21.0</version>
<version>1.22.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -338,7 +339,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>33.0.0-jre</version>
<version>33.1.0-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -360,7 +361,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2023.0.3</version>
<version>2023.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -372,12 +373,12 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.13</version>
<version>1.6.14</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.20</version>
<version>2.2.21</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -407,20 +408,12 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.12</version>
</dependency>
<!-- Specified so that Renovate will file Maven upgrade PRs, which
subsequently will cause `maven-enforcer-plugin` to require that
developers build the project using the latest Maven release. -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${version.maven}</version>
<version>1.14.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.21.1</version>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
@@ -466,7 +459,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>5.0.0</version>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
@@ -476,7 +469,7 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.7.1</version>
<version>2.8.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -490,19 +483,19 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.1.4</version>
<version>6.1.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>3.2.3</version>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.2.2</version>
<version>6.2.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -520,7 +513,7 @@
<plugin>
<groupId>com.github.ekryd.sortpom</groupId>
<artifactId>sortpom-maven-plugin</artifactId>
<version>3.4.0</version>
<version>3.4.1</version>
<configuration>
<createBackupFile>false</createBackupFile>
<encoding>${project.build.sourceEncoding}</encoding>
@@ -577,7 +570,7 @@
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
<version>3.6</version>
<version>3.7</version>
<configuration>
<bundledSignatures>
<bundledSignature>jdk-internal</bundledSignature>
@@ -624,7 +617,7 @@
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>8.0.1</version>
<version>8.0.2</version>
<configuration>
<injectAllReactorProjects>true</injectAllReactorProjects>
<runOnlyOnce>true</runOnlyOnce>
@@ -891,7 +884,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.14.0</version>
<version>10.15.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -916,7 +909,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<version>3.13.0</version>
<configuration>
<annotationProcessorPaths>
<!-- XXX: Inline and drop the version
@@ -1041,9 +1034,6 @@
<requireJavaVersion>
<version>${version.jdk}</version>
</requireJavaVersion>
<requireMavenVersion>
<version>${version.maven}</version>
</requireMavenVersion>
<requireNoRepositories />
<requirePluginVersions />
<requireUpperBoundDeps />
@@ -1068,7 +1058,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.2</version>
<executions>
<execution>
<id>sign-artifacts</id>
@@ -1382,7 +1372,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<version>0.8.12</version>
<configuration>
<excludes>
<!-- Refaster rules are tested using a custom method
@@ -1394,7 +1384,7 @@
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
<version>1.10.0</version>
<version>1.11.0</version>
<configuration>
<failOnError>false</failOnError>
<release>false</release>
@@ -1466,7 +1456,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.10.0.2594</version>
<version>3.11.0.3922</version>
</plugin>
</plugins>
</pluginManagement>

View File

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

View File

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

View File

@@ -166,7 +166,7 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
"Refaster Rule",
description.getLink(),
String.join(": ", description.checkName, description.getRawMessage()))
.overrideSeverity(severityOverride.orElse(description.severity()))
.overrideSeverity(severityOverride.orElseGet(description::severity))
.addAllFixes(description.fixes)
.build();
}

View File

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

View File

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

View File

@@ -1,8 +1,36 @@
# An overview of Error Prone Support releases, along with compatible Error
# Prone releases. This data was generated by `generate-version-compatibility-overview.sh`.
releases:
- version: 0.16.1
compatible:
- "2.26.1"
- "2.26.0"
- "2.25.0"
- "2.24.1"
- "2.24.0"
- "2.23.0"
- version: 0.16.0
compatible:
- "2.26.1"
- "2.26.0"
- "2.25.0"
- "2.24.1"
- "2.24.0"
- "2.23.0"
- version: 0.15.0
compatible:
- "2.26.1"
- "2.26.0"
- "2.25.0"
- "2.24.1"
- "2.24.0"
- "2.23.0"
- version: 0.14.0
compatible:
- "2.26.1"
- "2.26.0"
- "2.25.0"
- "2.24.1"
- "2.24.0"
- "2.23.0"
- "2.22.0"

View File

@@ -10,15 +10,21 @@ source "${HOME}/.sdkman/bin/sdkman-init.sh"
set -e -u -o pipefail
# Currently all released Error Prone Support versions are compatible with Java
# 17.
java_version=17.0.10-tem
(set +u && echo n | sdk install java "${java_version}")
sdk use java "${java_version}"
output_file="$(dirname "${0}")/_data/compatibility.yml"
ep_versions=$(
ep_versions="$(
git ls-remote \
--exit-code --refs --sort='-v:refname' \
https://github.com/google/error-prone.git \
'v*.*' \
| grep -oP '(?<=/v)[^/]+$'
)
)"
work_dir="$(mktemp -d)"
trap 'rm -rf -- "${work_dir=}"' INT TERM HUP EXIT
@@ -39,26 +45,43 @@ for eps_version in ${eps_versions}; do
(set +u && echo n | sdk install maven "${mvn_version}")
sdk use maven "${mvn_version}"
# Collect the list of checks supported by this version of Error Prone
# Support.
# XXX: Conditionally omit the `MethodReferenceUsage` exclusion once that
# check is production-ready.
mvn clean compile -Dverification.skip -DskipTests
checks="$(
find \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-print0 \
| xargs -0 grep -hoP '[^.]+$' \
| grep -v '^MethodReferenceUsage$' \
| paste -s -d ',' -
)"
# Remove any Error Prone flags used by this build that may not be compatible
# with the targeted version of Error Prone. Removal of these build flags does
# not influence the compatibility assessment.
sed -i -r 's,-XepAllSuggestionsAsWarnings|-Xep:\w+:\w+,,g' pom.xml
# Using each Error Prone release, attempt to build and test the source, while
# also applying the Maven Central-hosted Refaster rules. This determines
# source and behavioral (in)compatibility with Error Prone APIs, while also
# assessing whether the Refaster rules are deserialization-compatible.
# also applying the Maven Central-hosted Error Prone Support-defined checks
# and Refaster rules. This determines source and behavioral (in)compatibility
# with Error Prone APIs, while also assessing whether the Refaster rules are
# deserialization-compatible.
for ep_version in ${ep_versions}; do
echo "Testing Error Prone Support ${eps_version} with Error Prone ${ep_version}..."
mvn clean test \
-Perror-prone \
-Derror-prone.patch-checks=Refaster \
-Derror-prone.patch-checks="${checks}" \
-Ppatch \
-Pself-check \
-Dverification.skip \
-Dversion.error-prone-orig="${ep_version}" \
&& echo "SUCCESS: { \"eps_version\": \"${eps_version}\", \"ep_version\": \"${ep_version}\" }" || true
# Undo any changes applied by Refaster.
# Undo any changes applied by the checks.
git checkout -- '*.java'
done
done | tee "${build_log}"

108
workshop/README.md Normal file
View File

@@ -0,0 +1,108 @@
# Error Prone Workshop
XXX: @Rick update the slides.
## Initial setup of the workshop
1. Start by cloning this repository locally from GitHub.
2. Checkout the `workshop` branch.
3. Make sure to run `mvn clean install` in the root of this repository.
4. Open your code editor and familiarize yourself with the project structure.
In IntelliJ IDEA (or your preferred editor), try to run the
`WorkshopRefasterRulesTest` test. You might see the following error:
```
java: exporting a package from system module jdk.compiler is not allowed with --release
```
If this happens, go to _Settings -> Build, Execution, Deployment -> Compiler ->
Java Compiler_ and deselect the option _Use '--release' option for
cross-compilation (Java 9 and later)_.
Now the project is ready for the workshop.
## Part 1: Writing Refaster rules
During this part of the workshop we will implement multiple Refaster rules.
Go to the `workshop` module and open the
`tech.picnic.errorprone.workshop.refasterrules` package. There you can find one
example and 5 different exercises to do. Make sure to check out the
`WorkshopRefasterRulesTest.java` class where you can enable tests. Per
assignment there is a test in this class that one can enable (by dropping the
`@Disabled` annotation) to validate your changes. The goal is to implement or
improve the Refaster rules such that the enabled tests pass.
Tips:
* Go through the exercises in the proposed order.
* The `XXX:` comments explains what needs to happen.
* See the test cases for each Refaster rule by looking for the name of the
Refaster rule prefixed with `test`. For example, the
`WorkshopAssignment0Rules.java` rule collection has a Refaster rule named
`ExampleStringIsEmpty`. In the `WorkshopAssignment0RulesTestInput.java` and
`WorkshopAssignment0RulesTestOutput.java` files there is a
`testExampleStringIsEmpty` method that shows the input and output to test the
Refaster rule.
## Part 2: Writing Error Prone checks
During this part of the workshop we will implement parts of multiple Error
Prone `BugChecker`s. Each of these classes contain `XXX` comments explaining
what needs to be implemented. However, before diving in, make sure to first
navigate to a check's associated test class to drop the class-level `@Disabled`
annotation. Upon initial execution the tests will fail; the goal is to get then
to pass.
Some utility classes that you can use:
* `com.google.errorprone.util.ASTHelpers`: contains many common operations on
the Abstract Syntax Tree.
* `com.google.errorprone.fixes.SuggestedFixes`: contains helper methods for
creating `Fix`es.
To see the impact of the newly introduced Error Prone checks [re-trigger the
integration test framework](#validating-changes-with-the-integration-tests).
## Part 3 (optional): Validating changes with the integration tests
We created an integration testing framework that allows us to see the impact of
the rules that are created. This testing framework can be executed locally and
via GitHub Actions.
If you still have more than 10-15 minutes left, and you want to test this
locally, run the following commands:
```sh
mvn clean install -DskipTests -Dverification.skip
./integration-tests/checkstyle-10.12.4.sh
```
Once the process is complete, and changes are introduced, the following output
will be printed:
```
There are unexpected changes.
Inspect the changes here: /tmp/tmp.Cmr423L1pA/checkstyle-10.12.4-diff-of-diffs-changes.patch
```
This file is be provided for your review using your preferred text editor.
Alternatively, you can also navigate to the repository by going to the
`./integration-tests/.repos/checkstyle` directory and executing `git log -p` to
view the commit history and associated changes.
This shows the impact of the rules that you wrote when they are applied to
Checkstyle!
The other option is to execute the integration test via GitHub Actions. You
only need to commit and push to the branch. This will trigger execution of the
integration tests, which will run for about 10 minutes. When the build is
finished, go to the _Actions_ tab in your fork and navigate to your most recent
commit and click on it. Then click on _Summary_ and download the artifact
`integration-test-checkstyle-10.12.4` at the bottom. Once done, unzip the
artifact and inspect the `checkstyle-10.12.4-diff-of-diffs-changes.patch` file
to see the changes.
[eps-github]: https://github.com/PicnicSupermarket/error-prone-support
[eps-workshop-jfall]: https://drive.google.com/file/d/1Es1OuSUmPHSt3BjeCWfrXfoF-cJkkA1A/view

150
workshop/pom.xml Normal file
View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>workshop</artifactId>
<name>Picnic :: Error Prone Support :: Workshop</name>
<description>Project for the Error Prone Workshop.</description>
<url>https://error-prone.picnic.tech</url>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>error-prone-utils</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-11</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-templating</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleCompiler</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodTree;
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "Empty method can likely be deleted",
severity = WARNING,
tags = SIMPLIFICATION)
public final class Assignment0DeleteEmptyMethod extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link Assignment0DeleteEmptyMethod} instance. */
public Assignment0DeleteEmptyMethod() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Part 1: Ensure that we only delete methods that contain no statements.
// XXX: Part 2: Don't delete methods that are annotated with `@Override`.
return Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,61 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.nullLiteral;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodInvocationTree;
/**
* A {@link BugChecker} that flags AssertJ {@code isEqualTo(null)} checks for simplification.
*
* <p>This bug checker cannot be replaced with a simple Refaster rule, as the Refaster approach
* would require that all overloads of {@link org.assertj.core.api.Assert#isEqualTo(Object)} (such
* as {@link org.assertj.core.api.AbstractStringAssert#isEqualTo(String)}) are explicitly
* enumerated. This bug checker generically matches all such current and future overloads.
*/
@BugPattern(
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment1AssertJIsNullMethod extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.Assert").named("isEqualTo"),
argumentCount(1),
argument(0, nullLiteral()));
/** Instantiates a new {@link Assignment1AssertJIsNullMethod} instance. */
public Assignment1AssertJIsNullMethod() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
// This statement filters out `MethodInvocation`s that are *not* `assertThat().isEqualTo(null)`
// statements.
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fix = SuggestedFix.builder();
// XXX: Using `fix.merge(<some code>);` make sure we rename the method invocation to `isNull`.
// See the `SuggestedFixes` class ;).
tree.getArguments().forEach(arg -> fix.merge(SuggestedFix.delete(arg)));
return describeMatch(tree, fix.build());
}
}

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.utils.SourceCode;
/**
* A {@link BugChecker} that flags method invocations for which all arguments are wrapped using
* {@link org.mockito.Mockito#eq}; this is redundant.
*/
@BugPattern(
summary = "Don't unnecessarily use Mockito's `eq(...)`",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment2DropMockitoEq extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
/** Instantiates a new {@link Assignment2DropMockitoEq} instance. */
public Assignment2DropMockitoEq() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
// XXX: Make sure to return `Description.NO_MATCH` if the `tree` doesn't have arguments, or if
// the `isEqInvocation` method below returns `false` for at least one of the arguments.
SuggestedFix.Builder suggestedFix = SuggestedFix.builder();
for (ExpressionTree arg : tree.getArguments()) {
suggestedFix.replace(
arg,
SourceCode.treeToString(
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
}
return describeMatch(tree, suggestedFix.build());
}
@SuppressWarnings("UnusedMethod" /* Recommended to use when implementing this assignment. */)
private static boolean isEqInvocation(ExpressionTree tree, VisitorState state) {
return tree instanceof MethodInvocationTree && MOCKITO_EQ_METHOD.matches(tree, state);
}
}

View File

@@ -0,0 +1,54 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.List;
import tech.picnic.errorprone.utils.SourceCode;
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
@BugPattern(
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment3DropAutowiredConstructorAnnotation extends BugChecker
implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
/** Instantiates a new {@link Assignment3DropAutowiredConstructorAnnotation} instance. */
public Assignment3DropAutowiredConstructorAnnotation() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
// XXX: Using the `ASTHelpers#getConstructors` method, return `Description.NO_MATCH` if we do
// not have exactly 1 constructor (start by dropping `new ArrayList<>()` on the next line).
List<MethodTree> constructors = new ArrayList<>();
MultiMatchResult<AnnotationTree> hasAutowiredAnnotation =
AUTOWIRED_ANNOTATION.multiMatchResult(Iterables.getOnlyElement(constructors), state);
if (!hasAutowiredAnnotation.matches()) {
return Description.NO_MATCH;
}
AnnotationTree annotation = hasAutowiredAnnotation.onlyMatchingNode();
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
}
}

View File

@@ -0,0 +1,61 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import javax.lang.model.element.Modifier;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "JUnit method declaration can likely be improved",
severity = WARNING,
tags = SIMPLIFICATION)
@SuppressWarnings({
"UnusedMethod",
"UnusedVariable"
} /* This check is yet to be implemented as part of the demo. */)
public final class Assignment4JUnitTestMethodDeclaration extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(AT_LEAST_ONE, anyOf(isType("org.junit.jupiter.api.Test")));
/** Instantiates a new {@link Assignment4JUnitTestMethodDeclaration} instance. */
public Assignment4JUnitTestMethodDeclaration() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Part 1: Return `Description.NO_MATCH` if the method is not a `TEST_METHOD`.
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
// XXX: Part 2: Make sure that JUnit test methods don't use any of the modifiers from the
// `ILLEGAL_MODIFIERS` field, by using `SuggestedFixes#removeModifiers` and
// `SuggestedFix.Builder#merge`.
if (fixBuilder.isEmpty()) {
return Description.NO_MATCH;
}
return describeMatch(tree, fixBuilder.build());
}
}

View File

@@ -0,0 +1,108 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.TypesWithUndefinedEquality;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
/** A {@link BugChecker} that flags redundant identity conversions. */
@BugPattern(
summary = "Avoid or clarify identity conversions",
severity = WARNING,
tags = SIMPLIFICATION)
public final class Assignment5DeleteIdentityConversion extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableListMultimap",
"com.google.common.collect.ImmutableMap",
"com.google.common.collect.ImmutableMultimap",
"com.google.common.collect.ImmutableMultiset",
"com.google.common.collect.ImmutableRangeMap",
"com.google.common.collect.ImmutableRangeSet",
"com.google.common.collect.ImmutableSet",
"com.google.common.collect.ImmutableSetMultimap",
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClass("com.google.errorprone.matchers.Matchers")
.namedAnyOf("allOf", "anyOf"));
/** Instantiates a new {@link Assignment5DeleteIdentityConversion} instance. */
public Assignment5DeleteIdentityConversion() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
// XXX: Make sure we skip invocations that do not pass exactly one argument, by using the
// `tree`.
if (!IS_CONVERSION_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree sourceTree = arguments.get(0);
Type sourceType = ASTHelpers.getType(sourceTree);
Type resultType = ASTHelpers.getType(tree);
TargetType targetType = ASTHelpers.targetType(state);
if (sourceType == null || resultType == null || targetType == null) {
return Description.NO_MATCH;
}
if (!state.getTypes().isSameType(sourceType, resultType)
&& !isConvertibleWithWellDefinedEquality(sourceType, targetType.type(), state)) {
return Description.NO_MATCH;
}
if (sourceType.isPrimitive()
&& state.getPath().getParentPath().getLeaf() instanceof MemberSelectTree) {
return Description.NO_MATCH;
}
return buildDescription(tree)
// XXX: Use the `.addFix()` to suggest replacing the original `tree` with the `sourceTree`.
// Tip: You can get the actual String representation of a Tree by using the
// `SourceCode#treeToString`.
.build();
}
private static boolean isConvertibleWithWellDefinedEquality(
Type sourceType, Type targetType, VisitorState state) {
Types types = state.getTypes();
return !types.isSameType(targetType, OBJECT_TYPE.get(state))
&& types.isConvertible(sourceType, targetType)
&& Arrays.stream(TypesWithUndefinedEquality.values())
.noneMatch(b -> b.matchesType(sourceType, state) || b.matchesType(targetType, state));
}
}

View File

@@ -0,0 +1,22 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
/** Refaster rule used as example for the assignments of the workshop. */
final class WorkshopAssignment0Rules {
private WorkshopAssignment0Rules() {}
/** Prefer {@link String#isEmpty()} over alternatives that consult the string's length. */
static final class ExampleStringIsEmpty {
@BeforeTemplate
static boolean before(String string) {
return string.length() == 0;
}
@AfterTemplate
static boolean after(String string) {
return string.isEmpty();
}
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the first assignment of the workshop. */
final class WorkshopAssignment1Rules {
private WorkshopAssignment1Rules() {}
/** Prefer {@link String#String(char[])} over {@link String#copyValueOf(char[])}. */
static final class NewStringCharArray {
// XXX: Implement this Refaster rule.
}
}

View File

@@ -0,0 +1,34 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Collections;
import java.util.List;
/** Refaster rules for the second assignment of the workshop. */
final class WorkshopAssignment2Rules {
private WorkshopAssignment2Rules() {}
/**
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
static final class ImmutableListOfOne<T> {
@BeforeTemplate
static <T> List<T> before(T t) {
return ImmutableList.copyOf(
Refaster.anyOf(
Collections.singletonList(t),
List.of(t))
);
}
@AfterTemplate
static <T> List<T> after(T t) {
return ImmutableList.of(t);
}
}
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.base.Preconditions;
/** Refaster rules for the third assignment of the workshop. */
@SuppressWarnings("UnusedTypeParameter" /* Ignore this for demo purposes. */)
final class WorkshopAssignment3Rules {
private WorkshopAssignment3Rules() {}
// XXX: Tip: check the input and output files to see the *expected* refactoring.
/** Prefer {@link Preconditions#checkArgument(boolean)} over if statements. */
static final class CheckArgumentWithoutMessage {
// XXX: Implement the Refaster rule to get the test green.
}
/** Prefer {@link Preconditions#checkArgument(boolean, Object)} over if statements. */
static final class CheckArgumentWithMessage {
// XXX: Implement the Refaster rule to get the test green.
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the fourth assignment of the workshop. */
@SuppressWarnings("java:S1698" /* Reference comparison is valid for enums. */)
final class WorkshopAssignment4Rules {
private WorkshopAssignment4Rules() {}
// The test fails because non Enum comparisons are also rewritten.
// Fix the test by tweaking the type parameters.
// XXX: Get the test to pass by improving the Refaster rule (uncommented it first).
// static final class PrimitiveOrReferenceEqualityEnum<T> {
// @BeforeTemplate
// boolean before(T a, T b) {
// return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
// }
//
// @AfterTemplate
// @AlsoNegation
// boolean after(T a, T b) {
// return a == b;
// }
// }
}

View File

@@ -0,0 +1,12 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the fifth assignment of the workshop. */
@SuppressWarnings("UnusedTypeParameter" /* Ignore this for demo purposes. */)
final class WorkshopAssignment5Rules {
private WorkshopAssignment5Rules() {}
abstract static class StreamDoAllMatch<T> {
// XXX: Implement the Refaster rule to get the test green.
// Tip: use the `@Placeholder` annotation.
}
}

View File

@@ -0,0 +1,70 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment0Assignment0DeleteEmptyMethodTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment0DeleteEmptyMethod.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" Object m1() {",
" return null;",
" }",
"",
" void m2() {",
" System.out.println(42);",
" }",
"",
" // BUG: Diagnostic contains:",
" static void m3() {}",
"",
" interface F {",
" void fun();",
" }",
"}")
.addSourceLines(
"B.java",
"final class B implements A.F {",
" @Override",
" public void fun() {}",
"",
" /** Javadoc. */",
" // BUG: Diagnostic contains:",
" void m4() {}",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment0DeleteEmptyMethod.class, getClass())
.addInputLines(
"A.java",
"final class A {",
"",
" void instanceMethod() {}",
"",
" static void staticMethod() {}",
"",
" static void staticMethodWithComment() {",
" System.out.println(42);",
" }",
"}")
.addOutputLines(
"A.java",
"final class A {",
"",
" static void staticMethodWithComment() {",
" System.out.println(42);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,60 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment1AssertJIsNullMethodTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment1AssertJIsNullMethod.class, getClass())
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(1);",
" // BUG: Diagnostic contains:",
" assertThat(1).isEqualTo(null);",
" // BUG: Diagnostic contains:",
" assertThat(\"foo\").isEqualTo(null);",
" isEqualTo(null);",
" }",
"",
" private boolean isEqualTo(Object value) {",
" return value.equals(\"bar\");",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment1AssertJIsNullMethod.class, getClass())
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(null);",
" assertThat(\"foo\").isEqualTo(null);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isNull();",
" assertThat(\"foo\").isNull();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,102 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment2DropMockitoEqTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment2DropMockitoEq.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.ArgumentMatchers.notNull;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Runnable runnable = mock(Runnable.class);",
" doAnswer(inv -> null).when(runnable).run();",
"",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(notNull());",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(eq(0), notNull());",
" doAnswer(inv -> null).when(biConsumer).accept(notNull(), eq(\"foo\"));",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" // BUG: Diagnostic contains:",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment2DropMockitoEq.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(toString());",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(hashCode(), toString());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,106 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment3DropAutowiredConstructorAnnotationTest {
@Test
void identification() {
CompilationTestHelper.newInstance(
Assignment3DropAutowiredConstructorAnnotation.class, getClass())
.addSourceLines(
"Container.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" @Immutable",
" class A {",
" A() {}",
" }",
"",
" class B {",
" @Autowired",
" void setProperty(Object o) {}",
" }",
"",
" class C {",
" // BUG: Diagnostic contains:",
" @Autowired",
" C() {}",
" }",
"",
" class D {",
" // BUG: Diagnostic contains:",
" @Autowired",
" D(String x) {}",
" }",
"",
" class E {",
" @Autowired",
" E() {}",
"",
" E(String x) {}",
" }",
"",
" class F {",
" F() {}",
"",
" @Autowired",
" F(String x) {}",
" }",
"",
" class G {",
" @Autowired private Object o;",
" }",
"",
" class H {",
" @SafeVarargs",
" H(List<String>... lists) {}",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment3DropAutowiredConstructorAnnotation.class, getClass())
.addInputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" class A {",
" @Autowired",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
" @Autowired",
" B(String x) {}",
" }",
"}")
.addOutputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" class A {",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
" B(String x) {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,91 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this to validate part 1.")
final class Assignment4JUnitTestMethodDeclarationTest {
@Test
void identificationIllegalModifiers() {
CompilationTestHelper.newInstance(Assignment4JUnitTestMethodDeclaration.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void method1() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" public void method2() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" protected void method3() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" private void method4() {}",
"",
" public void method5() {}",
"",
" protected void method6() {}",
"",
" private void method7() {}",
"}")
.doTest();
}
@Test
void replacementIllegalModifiers() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment4JUnitTestMethodDeclaration.class, getClass())
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void foo() {}",
"",
" @Test",
" public void bar() {}",
"",
" @Test",
" protected void baz() {}",
"",
" @Test",
" private void qux() {}",
"",
" public void quux() {}",
"",
" private void quuz() {}",
"}")
.addOutputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void foo() {}",
"",
" @Test",
" void bar() {}",
"",
" @Test",
" void baz() {}",
"",
" @Test",
" void qux() {}",
"",
" public void quux() {}",
"",
" private void quuz() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,223 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment5DeleteIdentityConversionTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment5DeleteIdentityConversion.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"",
"import com.google.common.collect.ImmutableBiMap;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableListMultimap;",
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableMultimap;",
"import com.google.common.collect.ImmutableMultiset;",
"import com.google.common.collect.ImmutableRangeMap;",
"import com.google.common.collect.ImmutableRangeSet;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSetMultimap;",
"import com.google.common.collect.ImmutableTable;",
"import com.google.errorprone.matchers.Matcher;",
"import com.google.errorprone.matchers.Matchers;",
"",
"public final class A {",
" public void m() {",
" // BUG: Diagnostic contains:",
" Matcher allOf1 = Matchers.allOf(instanceMethod());",
" Matcher allOf2 = Matchers.allOf(instanceMethod(), staticMethod());",
" // BUG: Diagnostic contains:",
" Matcher anyOf1 = Matchers.anyOf(staticMethod());",
" Matcher anyOf2 = Matchers.anyOf(instanceMethod(), staticMethod());",
"",
" // BUG: Diagnostic contains:",
" Boolean b1 = Boolean.valueOf(Boolean.FALSE);",
" // BUG: Diagnostic contains:",
" Boolean b2 = Boolean.valueOf(false);",
" // BUG: Diagnostic contains:",
" boolean b3 = Boolean.valueOf(Boolean.FALSE);",
" // BUG: Diagnostic contains:",
" boolean b4 = Boolean.valueOf(false);",
"",
" // BUG: Diagnostic contains:",
" Byte byte1 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" Byte byte2 = Byte.valueOf(Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" byte byte3 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" byte byte4 = Byte.valueOf(Byte.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" Character c1 = Character.valueOf((Character) 'a');",
" // BUG: Diagnostic contains:",
" Character c2 = Character.valueOf('a');",
" // BUG: Diagnostic contains:",
" char c3 = Character.valueOf((Character) 'a');",
" // BUG: Diagnostic contains:",
" char c4 = Character.valueOf('a');",
"",
" // BUG: Diagnostic contains:",
" Double d1 = Double.valueOf((Double) 0.0);",
" // BUG: Diagnostic contains:",
" Double d2 = Double.valueOf(0.0);",
" // BUG: Diagnostic contains:",
" double d3 = Double.valueOf((Double) 0.0);",
" // BUG: Diagnostic contains:",
" double d4 = Double.valueOf(0.0);",
"",
" // BUG: Diagnostic contains:",
" Float f1 = Float.valueOf((Float) 0.0F);",
" // BUG: Diagnostic contains:",
" Float f2 = Float.valueOf(0.0F);",
" // BUG: Diagnostic contains:",
" float f3 = Float.valueOf((Float) 0.0F);",
" // BUG: Diagnostic contains:",
" float f4 = Float.valueOf(0.0F);",
"",
" // BUG: Diagnostic contains:",
" Integer i1 = Integer.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" Integer i2 = Integer.valueOf(1);",
" // BUG: Diagnostic contains:",
" int i3 = Integer.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" int i4 = Integer.valueOf(1);",
"",
" // BUG: Diagnostic contains:",
" Long l1 = Long.valueOf((Long) 1L);",
" // BUG: Diagnostic contains:",
" Long l2 = Long.valueOf(1L);",
" // BUG: Diagnostic contains:",
" long l3 = Long.valueOf((Long) 1L);",
" // BUG: Diagnostic contains:",
" long l4 = Long.valueOf(1L);",
"",
" Long l5 = Long.valueOf((Integer) 1);",
" Long l6 = Long.valueOf(1);",
" // BUG: Diagnostic contains:",
" long l7 = Long.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" long l8 = Long.valueOf(1);",
"",
" // BUG: Diagnostic contains:",
" Short s1 = Short.valueOf((Short) Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" Short s2 = Short.valueOf(Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" short s3 = Short.valueOf((Short) Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" short s4 = Short.valueOf(Short.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" String boolStr = Boolean.valueOf(Boolean.FALSE).toString();",
" int boolHash = Boolean.valueOf(false).hashCode();",
" // BUG: Diagnostic contains:",
" int byteHash = Byte.valueOf((Byte) Byte.MIN_VALUE).hashCode();",
" String byteStr = Byte.valueOf(Byte.MIN_VALUE).toString();",
"",
" String str1 = String.valueOf(0);",
" // BUG: Diagnostic contains:",
" String str2 = String.valueOf(\"1\");",
"",
" // BUG: Diagnostic contains:",
" ImmutableBiMap<Object, Object> o1 = ImmutableBiMap.copyOf(ImmutableBiMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableList<Object> o2 = ImmutableList.copyOf(ImmutableList.of());",
" ImmutableListMultimap<Object, Object> o3 =",
" // BUG: Diagnostic contains:",
" ImmutableListMultimap.copyOf(ImmutableListMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMap<Object, Object> o4 = ImmutableMap.copyOf(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultimap<Object, Object> o5 = ImmutableMultimap.copyOf(ImmutableMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultiset<Object> o6 = ImmutableMultiset.copyOf(ImmutableMultiset.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeMap<String, Object> o7 = ImmutableRangeMap.copyOf(ImmutableRangeMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeSet<String> o8 = ImmutableRangeSet.copyOf(ImmutableRangeSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSet<Object> o9 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSetMultimap<Object, Object> o10 =",
" // BUG: Diagnostic contains:",
" ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableTable<Object, Object, Object> o11 = ImmutableTable.copyOf(ImmutableTable.of());",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment5DeleteIdentityConversion.class, getClass())
.addInputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.copyOf(ImmutableList.of(1));",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.copyOf(ImmutableSet.of(1));",
" Collection<Integer> c2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.copyOf(ImmutableSet.of());",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.of();",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.of(1);",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.of(1);",
" Collection<Integer> c2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.of();",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,37 @@
package tech.picnic.errorprone.workshop.refasterrules;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollection;
final class WorkshopRefasterRulesTest {
@Test
void validateExampleRuleCollection() {
RefasterRuleCollection.validate(WorkshopAssignment0Rules.class);
}
@Test
void validateFirstWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment1Rules.class);
}
@Test
void validateSecondWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment2Rules.class);
}
@Test
void validateThirdWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment3Rules.class);
}
@Test
void validateFourthWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment4Rules.class);
}
@Test
void validateFifthWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment5Rules.class);
}
}

View File

@@ -0,0 +1,10 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment0RulesTest implements RefasterRuleCollectionTestCase {
boolean testExampleStringIsEmpty() {
boolean b = "foo".length() == 0;
return "bar".length() == 0;
}
}

View File

@@ -0,0 +1,10 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment0RulesTest implements RefasterRuleCollectionTestCase {
boolean testExampleStringIsEmpty() {
boolean b = "foo".isEmpty();
return "bar".isEmpty();
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment1RulesTest implements RefasterRuleCollectionTestCase {
String testNewStringCharArray() {
String.copyValueOf(new char[] {});
String.copyValueOf(new char[] {'f', 'o', 'o'});
return String.copyValueOf(new char[] {'b', 'a', 'r'});
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment1RulesTest implements RefasterRuleCollectionTestCase {
String testNewStringCharArray() {
new String(new char[] {});
new String(new char[] {'f', 'o', 'o'});
return new String(new char[] {'b', 'a', 'r'});
}
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment2RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
List<Object> testImmutableListOfOne() {
Collections.singletonList(1);
Collections.singletonList("foo");
List.of(1);
return List.of("bar");
}
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment2RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
List<Object> testImmutableListOfOne() {
ImmutableList.of(1);
ImmutableList.of("foo");
ImmutableList.of(1);
return ImmutableList.of("bar");
}
}

View File

@@ -0,0 +1,24 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException();
}
if (!"bar".isEmpty()) {
throw new IllegalArgumentException();
}
}
void testCheckArgumentWithMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException("The string is not empty");
}
if (!"bar".isEmpty()) {
throw new IllegalArgumentException(
"The rule should be able rewrite all kinds of messages ;).");
}
}
}

View File

@@ -0,0 +1,17 @@
package tech.picnic.errorprone.workshop.refasterrules;
import static com.google.common.base.Preconditions.checkArgument;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
checkArgument("foo".isEmpty());
checkArgument("bar".isEmpty());
}
void testCheckArgumentWithMessage() {
checkArgument("foo".isEmpty(), "The string is not empty");
checkArgument("bar".isEmpty(), "The rule should be able rewrite all kinds of messages ;).");
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Objects;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment4RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class);
}
ImmutableSet<Boolean> testPrimitiveOrReferenceEqualityEnum() {
// Don't pay attention to the `ImmutableSet#of` here, it's just syntactical sugar for the
// testing framework.
return ImmutableSet.of(
"foo".equals("bar"),
Integer.valueOf(1).equals(2),
RoundingMode.UP.equals(RoundingMode.DOWN),
Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
!RoundingMode.UP.equals(RoundingMode.DOWN),
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Objects;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment4RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class);
}
ImmutableSet<Boolean> testPrimitiveOrReferenceEqualityEnum() {
// Don't pay attention to the `ImmutableSet#of` here, it's just syntactical sugar for the
// testing framework.
return ImmutableSet.of(
"foo".equals("bar"),
Integer.valueOf(1).equals(2),
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN);
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment5RulesTest implements RefasterRuleCollectionTestCase {
boolean testStreamDoAllMatch() {
boolean example = Stream.of("foo").noneMatch(s -> !s.isBlank());
return Stream.of("bar").noneMatch(b -> !b.startsWith("b"));
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment5RulesTest implements RefasterRuleCollectionTestCase {
boolean testStreamDoAllMatch() {
boolean example = Stream.of("foo").allMatch(s -> s.isBlank());
return Stream.of("bar").allMatch(b -> b.startsWith("b"));
}
}