Compare commits

...

49 Commits

Author SHA1 Message Date
Stephan Schroevers
e94846ff67 Merge branch 'master' into website 2024-09-14 14:10:50 +02:00
Stephan Schroevers
b3ca01a6c7 Introduce RefasterRuleTestExtractor for documentation generation (#1317)
This new `Extractor` implementation collects Refaster example input and 
output code from rule collection tests.

This change also introduces explicit compilation steps for the test
code. As a side-effect this produces faster feedback in case of invalid
input or output code.
2024-09-14 12:02:37 +02:00
Picnic-DevPla-Bot
9f222e9efe Upgrade Error Prone 2.31.0 -> 2.32.0 (#1325)
See:
- https://github.com/google/error-prone/releases/tag/v2.32.0
- https://github.com/google/error-prone/compare/v2.31.0...v2.32.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.31.0-picnic-1...v2.32.0-picnic-1
2024-09-13 09:44:18 +02:00
Picnic-DevPla-Bot
097af51a3e Upgrade Project Reactor 2023.0.9 -> 2023.0.10 (#1323)
See:
- https://github.com/reactor/reactor/releases/tag/2023.0.10
- https://github.com/reactor/reactor/compare/2023.0.9...2023.0.10
2024-09-11 15:00:08 +02:00
Picnic-DevPla-Bot
c62e6c1127 Upgrade errorprone-slf4j 0.1.25 -> 0.1.28 (#1324)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.28
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.25...v0.1.28
2024-09-11 12:55:23 +02:00
Picnic-DevPla-Bot
5960423c4e Upgrade maven-dependency-plugin 3.7.1 -> 3.8.0 (#1304)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEP%20AND%20fixVersion%20%3E%203.7.1%20AND%20fixVersion%20%3C%3D%203.8.0
- https://github.com/apache/maven-dependency-plugin/releases/tag/maven-dependency-plugin-3.8.0
- https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.7.1...maven-dependency-plugin-3.8.0
2024-09-11 09:42:34 +02:00
Picnic-DevPla-Bot
188715c3c0 Upgrade Checkstyle 10.18.0 -> 10.18.1 (#1318)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.18.1
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.18.0...checkstyle-10.18.1
2024-09-11 09:25:26 +02:00
Picnic-DevPla-Bot
fb45bb00ed Upgrade maven-javadoc-plugin 3.8.0 -> 3.10.0 (#1310)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MJAVADOC%20AND%20fixVersion%20%3E%203.8.0%20AND%20fixVersion%20%3C%3D%203.10.0
- https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.8.0...maven-javadoc-plugin-3.10.0
2024-09-10 19:55:17 +02:00
Picnic-DevPla-Bot
fd5fc913ce Upgrade maven-install-plugin 3.1.2 -> 3.1.3 (#1301)
See:
- https://github.com/apache/maven-install-plugin/releases/tag/maven-install-plugin-3.1.3
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.1.2...maven-install-plugin-3.1.3
2024-09-10 19:44:45 +02:00
Picnic-DevPla-Bot
ae20c6069d Upgrade OpenRewrite 2.17.0 -> 2.18.0 (#1315)
See:
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v2.18.0
- https://github.com/openrewrite/rewrite-recipe-bom/compare/v2.17.0...v2.18.0
2024-09-10 19:14:25 +02:00
Picnic-DevPla-Bot
8457bd5026 Upgrade MongoDB driver 5.1.3 -> 5.1.4 (#1321)
See:
- https://jira.mongodb.org/issues/?jql=project%20%3D%20JAVA%20AND%20fixVersion%20%3E%204.11.3%20AND%20fixVersion%20%3C%3D%204.11.4
- https://github.com/mongodb/mongo-java-driver/releases/tag/r5.1.4
- https://github.com/mongodb/mongo-java-driver/compare/r5.1.3...r5.1.4
2024-09-10 18:07:43 +02:00
Picnic-DevPla-Bot
fcfb97b0e0 Upgrade maven-deploy-plugin 3.1.2 -> 3.1.3 (#1300)
See:
- https://github.com/apache/maven-deploy-plugin/releases/tag/maven-deploy-plugin-3.1.3
- https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.1.2...maven-deploy-plugin-3.1.3
2024-09-10 17:57:22 +02:00
Picnic-DevPla-Bot
15680b4cb3 Upgrade Surefire 3.3.1 -> 3.5.0 (#1298)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20SUREFIRE%20AND%20fixVersion%20%3E%203.3.1%20AND%20fixVersion%20%3C%3D%203.5.0
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.4.0
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.5.0
- https://github.com/apache/maven-surefire/compare/surefire-3.3.1...surefire-3.5.0
2024-09-10 17:05:51 +02:00
Picnic-DevPla-Bot
afebfbf478 Upgrade pomchecker-maven-plugin 1.11.0 -> 1.13.0 (#1319)
See:
- https://github.com/kordamp/pomchecker/releases/tag/v1.12.0
- https://github.com/kordamp/pomchecker/releases/tag/v1.13.0
- https://github.com/kordamp/pomchecker/compare/v1.11.0...v1.13.0
2024-09-10 13:32:08 +02:00
Picnic-DevPla-Bot
fe2ac938f3 Upgrade Checker Framework Annotations 3.46.0 -> 3.47.0 (#1322)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.47.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.46.0...checker-framework-3.47.0
2024-09-10 10:55:36 +02:00
Picnic-DevPla-Bot
078d8c16fa Upgrade OpenRewrite Templating 1.14.0 -> 1.14.1 (#1311)
See:
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.14.1
- https://github.com/openrewrite/rewrite-templating/compare/v1.14.0...v1.14.1
2024-09-04 15:01:36 +02:00
Stephan Schroevers
c679a3fc0c Improve Optional#orElse{,Get} support (#1283)
Summary of changes:
- Consolidate the `OptionalOrElseGet` Refaster rule and the 
  `OptionalOrElse` bug checker into the latter, keeping the best of
  both.
- Rename the `OptionalOrElse` bug checker to `OptionalOrElseGet` to
  avoid confusion.
- Replace the `IsLikelyTrivialComputation` matcher with the inverse
  `RequiresComputation` variant.
- Introduce an `OptionalOrElse` Refaster rule that simplifies
  expressions in the "opposite direction".
2024-09-03 16:00:33 +02:00
Stephan Schroevers
de54b4bf64 Introduce assorted unsigned int/long Refaster rules (#1291) 2024-09-03 15:35:15 +02:00
Picnic-DevPla-Bot
ea60241782 Upgrade Maven 3.9.8 -> 3.9.9 (#1295)
See:
- https://maven.apache.org/release-notes-all.html
- https://github.com/apache/maven/compare/maven-3.9.8...maven-3.9.9
2024-09-03 15:26:41 +02:00
Picnic-Bot
e4f928addb Upgrade fmt-maven-plugin 2.22.1 -> 2.24 (#1069)
See:
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.23
- https://github.com/spotify/fmt-maven-plugin/releases/tag/2.24
- https://github.com/spotify/fmt-maven-plugin/compare/2.22.1...2.24
2024-09-03 13:13:18 +02:00
Picnic-DevPla-Bot
059cc9e2db Upgrade Error Prone 2.30.0 -> 2.31.0 (#1314)
See:
- https://github.com/google/error-prone/releases/tag/v2.31.0
- https://github.com/google/error-prone/compare/v2.30.0...v2.31.0
2024-09-03 12:19:04 +02:00
Picnic-DevPla-Bot
1e43c28c95 Upgrade Byte Buddy 1.15.0 -> 1.15.1 (#1316)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.15.1
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.15.0...byte-buddy-1.15.1
2024-09-02 21:10:41 +02:00
Stephan Schroevers
19121d647f Undo Refaster test package split, glue it together 2024-08-31 18:50:22 +02:00
Stephan Schroevers
379b4d603f Introduce RefasterRuleTestExtractor for documentation generation
This new `Extractor` implementation collects Refaster example input and
output code from rule collection tests.

This change also introduces explicit compilation steps for the test
code. As a side-effect this produces faster feedback in case of invalid
input or output code.

(cherry picked from commit c8c51078332388055746b433318148f428239c18)
2024-08-31 18:37:42 +02:00
Picnic-DevPla-Bot
a07e9b3115 Upgrade Swagger 2.2.22 -> 2.2.23 (#1312)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.23
- https://github.com/swagger-api/swagger-core/compare/v2.2.22...v2.2.23
2024-08-29 09:23:09 +02:00
Picnic-DevPla-Bot
aec38d2e33 Upgrade Mockito 5.12.0 -> 5.13.0 (#1309)
See:
- https://github.com/mockito/mockito/releases/tag/v5.13.0
- https://github.com/mockito/mockito/compare/v5.12.0...v5.13.0
2024-08-28 09:32:16 +02:00
Picnic-DevPla-Bot
5b77663288 Upgrade Spring 6.1.11 -> 6.1.12 (#1288)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v6.1.12
- https://github.com/spring-projects/spring-framework/compare/v6.1.11...v6.1.12
2024-08-27 15:48:14 +02:00
Stephan Schroevers
97c2bbd4b1 Introduce MonoFromFutureAsyncLoadingCacheGet Refaster rule (#1290) 2024-08-26 16:33:57 +02:00
Picnic-DevPla-Bot
9c8fbfd36a Upgrade OpenRewrite Templating 1.13.0 -> 1.14.0 (#1294)
See:
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.14.0
- https://github.com/openrewrite/rewrite-templating/compare/v1.13.0...v1.14.0
2024-08-26 13:42:53 +02:00
Picnic-DevPla-Bot
38e6c3fdb5 Upgrade OpenRewrite 2.16.0 -> 2.17.0 (#1286)
See:
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v2.17.0
- https://github.com/openrewrite/rewrite-recipe-bom/compare/v2.16.0...v2.17.0
2024-08-26 13:24:42 +02:00
Picnic-DevPla-Bot
5b1d82cfeb Upgrade Guava 33.2.1-jre -> 33.3.0-jre (#1293)
And drop some Refaster rules now covered by `@InlineMe` instructions.

See:
- https://guava.dev/releases/33.3.0-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v33.3.0
- https://github.com/google/guava/compare/v33.2.0...v33.3.0
2024-08-26 13:02:15 +02:00
Picnic-DevPla-Bot
0821a95fcc Upgrade Spring Security 6.3.1 -> 6.3.3 (#1303)
See:
- https://github.com/spring-projects/spring-security/releases/tag/6.3.2
- https://github.com/spring-projects/spring-security/releases/tag/6.3.3
- https://github.com/spring-projects/spring-security/compare/6.3.1...6.3.3
2024-08-26 12:50:25 +02:00
Picnic-DevPla-Bot
f4afe457cb Upgrade Checkstyle 10.17.0 -> 10.18.0 (#1308)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.18.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.17.0...checkstyle-10.18.0
2024-08-26 09:19:52 +02:00
Picnic-DevPla-Bot
fd56ca8b6e Upgrade Spring Boot 3.3.2 -> 3.3.3 (#1305)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v3.3.3
- https://github.com/spring-projects/spring-boot/compare/v3.3.2...v3.3.3
2024-08-26 09:04:43 +02:00
Picnic-DevPla-Bot
882794d63b Upgrade maven-checkstyle-plugin 3.4.0 -> 3.5.0 (#1307)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.4.0%20AND%20fixVersion%20%3C%3D%203.5.0
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.4.0...maven-checkstyle-plugin-3.5.0
2024-08-26 08:22:34 +02:00
Mohamed Sameh
73ed6e32c7 Introduce Collections{Max,Min,Sort} Refaster rules (#1297)
While there, rename `{Max,Min}OfCollection` to
`Collections{Max,Min}WithComparator`.
2024-08-26 07:38:26 +02:00
Picnic-DevPla-Bot
ca5c3dd3b4 Upgrade Byte Buddy 1.14.19 -> 1.15.0 (#1306)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.15.0
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.19...byte-buddy-1.15.0
2024-08-26 07:28:00 +02:00
Stephan Schroevers
66c485e14d This is a squash commit of the following previous commits:
Try fix build

    ---

    Post-rebase fix

    ---

    Publish Error Prone compatibility matrix on website

    The new `website/generate-version-compatibility-overview.sh` script
    tests all combinations, and stores the result in a Jekyll data file.

    ---

    Some cleanup

    ---

    Make tests more maintainable

    ---

    Try to fix htmlproofer

    ---

    Fix source links

    ---

    Introduce `BugPatternTestExtractor` with tests

    ---

    Simplify tests

    ---

    Further simplify testing setup

    ---

    Kill another mutant and drop unused imports

    ---

    This actually kills the mutant

    ---

    Suggestions

    ---

    Suggestions

    ---

    Suggestions

    ---

    Fix JDK 11 compatibility

    ---

    PSM-1717 Pass `ClassLoader` to `ServiceLoader`

    ---

    Introduce documentation generation

    This is a squash commit of the following previous commits:

    ---

    Introduce `documentation-support` module to extract website data from source code

    By adding a compilation `TaskListener` that extracts data from the Bug pattern
    and Refaster rule collection (test) classes and writing to JSON output files
    in the target directory. This extraction happens as part of the Maven build
    using the `docgen` profile.

    ---

    Improve website styling

    Co-authored-by: japborst <japborst@gmail.com>
    Co-authored-by: Gijs de Jong <berichtaangijs@gmail.com>

    ---

    Generate Markdown files from existing content for the website

    ---

    Upgrade dependencies to the latest versions

    ---

    Compile and install project jars before docgen

    ---

    Run validation in build and exclude self-url

    ---

    Reintroduce `htmlproofer`, improve templates, cleanup setup and dependencies

    ---

    Delete directory if it exists

    ---

    Add SCSS for GitHub button and fix bug pattern sample rendering

    ---

    Fix bug pattern GitHub link

    ---

    Small styling tweaks (incl. for dark theme)

    ---

    Use single mvn command

    ---

    Move mustache templates

    ---

    Hardcode anchors for headings

    ---

    Add supression to bugpatterns

    ---

    Remove self ignore for html-proofer

    ---

    Add refaster supressions and use callouts

    ---

    Use v0.4.1-SNAPSHOT

    ---

    Revert "Use single mvn command"

    This reverts commit 594471d1ed23a1c19d7fe88d925d1b7f828716cd.

    ---

    Extract Refaster samples from source code instead of AST

    ---

    Skip verification, for now

    ---

    Add notes on disabling bugpatterns

    ---

    Set default layout and image

    ---

    Fix mobile navigation

    ---

    Revert "Set default layout and image"

    This reverts commit 67a4aa7b5b4d14c0f2b783f345f53affe6ef3ec5.

    ---

    Add supression for refaster rules

    ---

    Post-rebase fixes

    ---

    Fix the tests

    ---

    Doh

    ---

    Version bump

    ---

    Exclude ThirdPartyLibraryTest from Bug Pattern test output

    ---

    Remove only last occurence of 'Test' in Bug Pattern tests doc generation

    ---

    Move `MapRulesTest` resources

    ---

    Add exclusion for docgen of `SourceCodeTest`

    ---

    Bump version

    ---

    Extra fixes after rebase

    ---

    Delete custom nav footer and `assets/images/favicon.ico`

    ---

    Post rebase fixes with version bump to `0.8.1-SNAPSHOT`

    ---

    Use new BugPatternTest extraction method

    ---

    Fit Refaster extractors into new documentation support setup

    ---

    Drop Docgen Maven profile

    ---

    Move new rule collection

    ---

    Improve extractor matching and update README

    ---

    Move `SuggestedFixRules` test files

    ---

    Fix Refaster exclusion regexes

    ---

    Disable external link checking until the `website` branch is up-to-date with `master` again

    ---

    Post-rebase fix

    ---

    Not sure why `-Dverification.skip` fails while `-Dverification.warn` doesn't; won't investigate right now

    ---

    Check external links again

    ---

    Upgrade dependencies

    ---

    WIP: towards dropping Mustache

    ---

    WIP: Java doc generator

    ---

    WIP: switch over

    ---

    WIP: Cleanup

    ---

    Polish
2024-08-25 14:41:42 +02:00
Picnic-DevPla-Bot
f6e9dbb996 Upgrade Byte Buddy 1.14.18 -> 1.14.19 (#1292)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.19
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.18...byte-buddy-1.14.19
2024-08-23 19:39:59 +02:00
Picnic-DevPla-Bot
260021c961 Upgrade NullAway 0.11.1 -> 0.11.2 (#1299)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/releases/tag/v0.11.2
- https://github.com/uber/NullAway/compare/v0.11.1...v0.11.2
2024-08-23 19:31:03 +02:00
Stephan Schroevers
ec54e79f3e Introduce {Max,Min}Of{Array,Collection} Refaster rules (#1276)
While there, generalize the `{Max,Min}OfVarargs` rules.
2024-08-23 08:47:35 +02:00
Stephan Schroevers
4cbfdba521 Introduce additional File and Path Refaster rules (#1282) 2024-08-22 09:58:09 +02:00
Picnic-DevPla-Bot
d94f65a1f0 Upgrade JUnit 5 5.10.3 -> 5.11.0 (#1289)
See:
- https://junit.org/junit5/docs/current/release-notes/
- https://github.com/junit-team/junit5/releases/tag/r5.11.0-M1
- https://github.com/junit-team/junit5/releases/tag/r5.11.0-M2
- https://github.com/junit-team/junit5/releases/tag/r5.11.0-RC1
- https://github.com/junit-team/junit5/releases/tag/r5.11.0
- https://github.com/junit-team/junit5/compare/r5.10.3...r5.11.0
2024-08-15 09:21:45 +02:00
Picnic-DevPla-Bot
9afdf2ddf3 Upgrade OpenRewrite Templating 1.12.3 -> 1.13.0 (#1287)
See:
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.13.0
- https://github.com/openrewrite/rewrite-templating/compare/v1.12.3...v1.13.0
2024-08-14 11:35:36 +02:00
Picnic-DevPla-Bot
060c901479 Upgrade maven-gpg-plugin 3.2.4 -> 3.2.5 (#1284)
See:
- https://github.com/apache/maven-gpg-plugin/releases/tag/maven-gpg-plugin-3.2.5
- https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.2.4...maven-gpg-plugin-3.2.5
2024-08-14 08:56:02 +02:00
Picnic-DevPla-Bot
1feee4f64a Upgrade Project Reactor 2023.0.8 -> 2023.0.9 (#1285)
See:
- https://github.com/reactor/reactor/releases/tag/2023.0.9
- https://github.com/reactor/reactor/compare/2023.0.8...2023.0.9
2024-08-14 08:26:16 +02:00
Picnic-Bot
552ddf6a7d Upgrade Maven API 3.9.5 -> 3.9.8 (#701)
See:
- https://maven.apache.org/release-notes-all.html
- https://github.com/apache/maven/releases/tag/maven-3.9.6
- https://github.com/apache/maven/releases/tag/maven-3.9.7
- https://github.com/apache/maven/releases/tag/maven-3.9.8
- https://github.com/apache/maven/compare/maven-3.9.5...maven-3.9.8
2024-08-12 16:14:46 +02:00
Stephan Schroevers
5d92c6c6ce Update Error Prone compatibility matrix (#1281) 2024-08-12 08:28:56 +02:00
Stephan Schroevers
fa8ca80040 [maven-release-plugin] prepare for next development iteration 2024-08-11 15:05:54 +02:00
64 changed files with 2112 additions and 409 deletions

View File

@@ -46,7 +46,7 @@ jobs:
with:
java-version: ${{ matrix.jdk }}
java-distribution: ${{ matrix.distribution }}
maven-version: 3.9.6
maven-version: 3.9.9
- name: Display build environment details
run: mvn --version
- name: Build project against vanilla Error Prone, compile Javadoc

View File

@@ -38,7 +38,7 @@ jobs:
with:
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
maven-version: 3.9.9
- name: Initialize CodeQL
uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
with:

View File

@@ -38,26 +38,29 @@ jobs:
www.bestpractices.dev:443
www.youtube.com:443
youtrack.jetbrains.com:443
- name: Check out code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
with:
persist-credentials: false
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0
with:
working-directory: ./website
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
- name: Compile project and extract data
run: mvn -T1C clean install -DskipTests -Dverification.skip
- name: Generate documentation
run: ./generate-docs.sh
run: mvn exec:java@generate-docs -pl documentation-support
- name: Build website with Jekyll
working-directory: ./website
run: bundle exec jekyll build
- name: Validate HTML output
working-directory: ./website
# XXX: Drop `--disable_external true` once we fully adopted the
# "Refaster rules" terminology on our website and in the code.
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
# XXX: StackOverflow returns a 403 when run on GHA.
run: bundle exec htmlproofer --no-check-external-hash --swap-url 'https\://error-prone.picnic.tech:' --ignore-urls '/^https:\/\/stackoverflow.com\/.*/' ./_site
- name: Upload website as artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
with:

View File

@@ -27,7 +27,7 @@ jobs:
checkout-fetch-depth: 2
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
maven-version: 3.9.9
- name: Run Pitest
# By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only
# analyzes lines changed in the associated pull request, as GitHub

View File

@@ -35,7 +35,7 @@ jobs:
with:
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
maven-version: 3.9.9
- name: Download Pitest analysis artifact
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
with:

View File

@@ -38,7 +38,7 @@ jobs:
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
maven-version: 3.9.9
- name: Install project to local Maven repository
run: mvn -T1C install -DskipTests -Dverification.skip
- name: Run integration test

View File

@@ -40,7 +40,7 @@ jobs:
checkout-fetch-depth: 0
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
maven-version: 3.9.9
- name: Create missing `test` directory
# XXX: Drop this step in favour of actually having a test.
run: mkdir refaster-compiler/src/test

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.18.1-SNAPSHOT</version>
</parent>
<artifactId>documentation-support</artifactId>
@@ -33,14 +33,31 @@
<artifactId>error_prone_test_helpers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>error-prone-utils</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
@@ -67,6 +84,10 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
@@ -95,4 +116,29 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-docs</id>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>tech.picnic.errorprone.documentation.JekyllCollectionGenerator</mainClass>
<arguments>
<argument>${maven.multiModuleProjectDirectory}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -27,7 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
/**
* An {@link Extractor} that describes how to extract data from classes that test a {@code
@@ -40,7 +40,7 @@ import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
@Immutable
@AutoService(Extractor.class)
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
public final class BugPatternTestExtractor implements Extractor<TestCases> {
public final class BugPatternTestExtractor implements Extractor<BugPatternTestCases> {
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
public BugPatternTestExtractor() {}
@@ -50,7 +50,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
}
@Override
public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
public Optional<BugPatternTestCases> tryExtract(ClassTree tree, VisitorState state) {
BugPatternTestCollector collector = new BugPatternTestCollector();
collector.scan(tree, state);
@@ -59,7 +59,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
.filter(not(ImmutableList::isEmpty))
.map(
tests ->
new AutoValue_BugPatternTestExtractor_TestCases(
new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
state.getPath().getCompilationUnit().getSourceFile().toUri(),
ASTHelpers.getSymbol(tree).className(),
tests));
@@ -95,10 +95,10 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.namedAnyOf("addOutputLines", "expectUnchanged");
private final List<TestCase> collectedTestCases = new ArrayList<>();
private final List<BugPatternTestCase> collectedBugPatternTestCases = new ArrayList<>();
private ImmutableList<TestCase> getCollectedTests() {
return ImmutableList.copyOf(collectedTestCases);
private ImmutableList<BugPatternTestCase> getCollectedTests() {
return ImmutableList.copyOf(collectedBugPatternTestCases);
}
@Override
@@ -110,14 +110,14 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
classUnderTest -> {
List<TestEntry> entries = new ArrayList<>();
if (isReplacementTest) {
extractReplacementTestCases(node, entries, state);
extractReplacementBugPatternTestCases(node, entries, state);
} else {
extractIdentificationTestCases(node, entries, state);
extractIdentificationBugPatternTestCases(node, entries, state);
}
if (!entries.isEmpty()) {
collectedTestCases.add(
new AutoValue_BugPatternTestExtractor_TestCase(
collectedBugPatternTestCases.add(
new AutoValue_BugPatternTestExtractor_BugPatternTestCase(
classUnderTest, ImmutableList.copyOf(entries).reverse()));
}
});
@@ -140,7 +140,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
: Optional.empty();
}
private static void extractIdentificationTestCases(
private static void extractIdentificationBugPatternTestCases(
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) {
String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
@@ -155,11 +155,11 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
if (receiver instanceof MethodInvocationTree methodInvocation) {
extractIdentificationTestCases(methodInvocation, sink, state);
extractIdentificationBugPatternTestCases(methodInvocation, sink, state);
}
}
private static void extractReplacementTestCases(
private static void extractReplacementBugPatternTestCases(
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) {
/*
@@ -185,7 +185,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
if (receiver instanceof MethodInvocationTree methodInvocation) {
extractReplacementTestCases(methodInvocation, sink, state);
extractReplacementBugPatternTestCases(methodInvocation, sink, state);
}
}
@@ -208,24 +208,26 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
}
@AutoValue
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCases.class)
abstract static class TestCases {
static TestCases create(URI source, String testClass, ImmutableList<TestCase> testCases) {
return new AutoValue_BugPatternTestExtractor_TestCases(source, testClass, testCases);
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCases.class)
abstract static class BugPatternTestCases {
static BugPatternTestCases create(
URI source, String testClass, ImmutableList<BugPatternTestCase> testCases) {
return new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
source, testClass, testCases);
}
abstract URI source();
abstract String testClass();
abstract ImmutableList<TestCase> testCases();
abstract ImmutableList<BugPatternTestCase> testCases();
}
@AutoValue
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCase.class)
abstract static class TestCase {
static TestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
return new AutoValue_BugPatternTestExtractor_TestCase(classUnderTest, entries);
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCase.class)
abstract static class BugPatternTestCase {
static BugPatternTestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
return new AutoValue_BugPatternTestExtractor_BugPatternTestCase(classUnderTest, entries);
}
abstract String classUnderTest();

View File

@@ -14,7 +14,6 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ServiceLoader;
import javax.tools.JavaFileObject;
@@ -87,6 +86,6 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
}
private static String getSimpleClassName(URI path) {
return Paths.get(path).getFileName().toString().replace(".java", "");
return Path.of(path).getFileName().toString().replace(".java", "");
}
}

View File

@@ -0,0 +1,332 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableTable.toImmutableTable;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
import com.github.difflib.DiffUtils;
import com.github.difflib.UnifiedDiffUtils;
import com.github.difflib.patch.Patch;
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern.SeverityLevel;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCase;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.IdentificationTestEntry;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.ReplacementTestEntry;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestEntry;
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase;
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
/**
* A command line utility that produces configuration files for the Jekyll-based Error Prone Support
* website.
*/
// XXX: Expand the class documentation.
// XXX: Rename this class. Then also update the reference in `website/.gitignore`.
// XXX: Now that we have bug checkers in multiple Maven modules, we should
// likely document the source of each check on the website, perhaps even
// grouping them by module.
public final class JekyllCollectionGenerator {
// XXX: Find a bette name. Also, externalize this.
private static final PathMatcher PATH_MATCHER =
FileSystems.getDefault().getPathMatcher("glob:**/target/docs/*.json");
// XXX: Review class setup.
private JekyllCollectionGenerator() {}
/**
* Runs the application.
*
* @param args Arguments to the application; must specify the path to the Error Prone Support
* project root, and nothing else.
* @throws IOException If any file could not be read or written.
*/
public static void main(String[] args) throws IOException {
checkArgument(args.length == 1, "Precisely one project root path must be provided");
Path projectRoot = Path.of(args[0]).toAbsolutePath();
generateIndex(projectRoot);
PageGenerator.apply(projectRoot);
}
private static void generateIndex(Path projectRoot) throws IOException {
try (BufferedWriter writer =
Files.newBufferedWriter(projectRoot.resolve("website").resolve("index.md"), UTF_8)) {
writer.write("---");
writer.newLine();
writer.write("layout: default");
writer.newLine();
writer.write("title: Home");
writer.newLine();
writer.write("nav_order: 1");
writer.newLine();
writer.write("---");
writer.newLine();
writer.write(
Files.readString(projectRoot.resolve("README.md")).replace("=\"website/", "=\""));
}
}
// XXX: Review this class should be split in two: one for bug patterns and one for Refaster rules.
private static final class PageGenerator extends SimpleFileVisitor<Path> {
private static final Splitter LINE_SPLITTER = Splitter.on(System.lineSeparator());
private static final YAMLMapper YAML_MAPPER =
YAMLMapper.builder()
.visibility(PropertyAccessor.FIELD, Visibility.ANY)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)
.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
.enable(YAMLGenerator.Feature.USE_PLATFORM_LINE_BREAKS)
.build();
private final List<BugPatternDocumentation> bugPatterns = new ArrayList<>();
private final List<BugPatternTestCases> bugPatternTests = new ArrayList<>();
private final List<RefasterTestCases> refasterRuleCollectionTests = new ArrayList<>();
static void apply(Path projectRoot) throws IOException {
PageGenerator pageGenerator = new PageGenerator();
Files.walkFileTree(projectRoot, pageGenerator);
pageGenerator.writePages(projectRoot);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (!PATH_MATCHER.matches(file)) {
return FileVisitResult.CONTINUE;
}
// XXX: If we use a consistent ID separator, then this can become a switch statement. Now we
// depend on evaluation order.
// XXX: Alternatively, use polymorphism and let Jackson figure it out.
// XXX: If we stick with an ID-based approach, then deduplicate the ID references here and in
// the `Extractor` implementations.
String fileName = file.getFileName().toString();
if (fileName.startsWith("bugpattern-test")) {
bugPatternTests.add(Json.read(file, BugPatternTestCases.class));
} else if (fileName.startsWith("bugpattern")) {
bugPatterns.add(Json.read(file, BugPatternDocumentation.class));
} else if (fileName.startsWith("refaster-rule-collection-test")) {
refasterRuleCollectionTests.add(Json.read(file, RefasterTestCases.class));
} else {
// XXX: Handle differently?
throw new IllegalStateException("Unexpected file: " + fileName);
}
return FileVisitResult.CONTINUE;
}
private void writePages(Path projectRoot) throws IOException {
Path website = projectRoot.resolve("website");
writePages(
website.resolve("_bugpatterns"),
getJekyllBugPatternDescriptions(projectRoot),
JekyllBugPatternDescription::name);
writePages(
website.resolve("_refasterrules"),
getJekyllRefasterRuleCollectionDescription(),
JekyllRefasterRuleCollectionDescription::name);
}
private static <T> void writePages(
Path directory, ImmutableList<T> documents, Function<T, String> nameExtractor)
throws IOException {
for (T document : documents) {
Files.createDirectories(directory);
try (BufferedWriter writer =
Files.newBufferedWriter(
directory.resolve(nameExtractor.apply(document) + ".md"), UTF_8)) {
YAML_MAPPER.writeValue(writer, document);
writer.write("---");
writer.newLine();
}
}
}
private ImmutableList<JekyllBugPatternDescription> getJekyllBugPatternDescriptions(
Path projectRoot) {
ImmutableListMultimap<String, TestEntry> bugPatternTestCases =
bugPatternTests.stream()
.flatMap(testCases -> testCases.testCases().stream())
.collect(
flatteningToImmutableListMultimap(
BugPatternTestCase::classUnderTest, t -> t.entries().stream()));
return bugPatterns.stream()
.map(
b ->
new AutoValue_JekyllCollectionGenerator_JekyllBugPatternDescription(
b.name(),
b.name(),
b.summary(),
b.severityLevel(),
b.tags(),
// XXX: Derive `Path` from filesytem.
Path.of(b.source()).relativize(projectRoot).toString(),
bugPatternTestCases.get(b.fullyQualifiedName()).stream()
.filter(t -> t.type() == TestEntry.TestType.IDENTIFICATION)
.map(t -> ((IdentificationTestEntry) t).code())
.collect(toImmutableList()),
bugPatternTestCases.get(b.fullyQualifiedName()).stream()
.filter(t -> t.type() == TestEntry.TestType.REPLACEMENT)
.map(t -> generateDiff((ReplacementTestEntry) t))
.collect(toImmutableList())))
.collect(toImmutableList());
}
private ImmutableList<JekyllRefasterRuleCollectionDescription>
getJekyllRefasterRuleCollectionDescription() {
ImmutableTable<String, Boolean, List<RefasterTestCase>> refasterTests =
refasterRuleCollectionTests.stream()
.collect(
toImmutableTable(
RefasterTestCases::ruleCollection,
RefasterTestCases::isInput,
RefasterTestCases::testCases));
return refasterTests.rowMap().entrySet().stream()
.map(
c ->
new AutoValue_JekyllCollectionGenerator_JekyllRefasterRuleCollectionDescription(
c.getKey(),
c.getKey(),
// XXX: Derive severity from input.
SUGGESTION,
// XXX: Derive tags from input (or drop this feature).
ImmutableList.of("Simplification"),
// XXX: Derive source location from input.
String.format(
"error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/%s.java",
c.getKey()),
getRules(c.getValue().get(true), c.getValue().get(false))))
.collect(toImmutableList());
}
private static ImmutableList<JekyllRefasterRuleCollectionDescription.Rule> getRules(
@Nullable List<RefasterTestCase> inputTests, @Nullable List<RefasterTestCase> outputTests) {
ImmutableMap<String, String> inputs = indexRefasterTestData(inputTests);
ImmutableMap<String, String> outputs = indexRefasterTestData(outputTests);
return Sets.intersection(inputs.keySet(), outputs.keySet()).stream()
.map(
name ->
new AutoValue_JekyllCollectionGenerator_JekyllRefasterRuleCollectionDescription_Rule(
name,
// XXX: Derive severity from input.
SUGGESTION,
// XXX: Derive tags from input (or drop this feature).
ImmutableList.of("Simplification"),
generateDiff(
requireNonNull(inputs.get(name), "Input"),
requireNonNull(outputs.get(name), "Output"))))
.collect(toImmutableList());
}
private static ImmutableMap<String, String> indexRefasterTestData(
@Nullable List<RefasterTestCase> data) {
return data == null
? ImmutableMap.of()
: data.stream()
.collect(toImmutableMap(RefasterTestCase::name, RefasterTestCase::content));
}
private static String generateDiff(ReplacementTestEntry testEntry) {
return generateDiff(testEntry.input(), testEntry.output());
}
private static String generateDiff(String before, String after) {
// XXX: Extract splitter.
List<String> originalLines = LINE_SPLITTER.splitToList(before);
List<String> replacementLines = LINE_SPLITTER.splitToList(after);
Patch<String> diff = DiffUtils.diff(originalLines, replacementLines);
return UnifiedDiffUtils.generateUnifiedDiff(
"", "", originalLines, diff, Integer.MAX_VALUE / 2)
.stream()
.skip(3)
.collect(joining(System.lineSeparator()));
}
}
@AutoValue
abstract static class JekyllBugPatternDescription {
// XXX: Make this a derived property?
abstract String title();
abstract String name();
abstract String summary();
abstract SeverityLevel severity();
abstract ImmutableList<String> tags();
// XXX: The documentation could link to the original test code. Perhaps even with the correct
// line numbers.
abstract String source();
// XXX: The `identification` and `replacement` fields have odd names.
abstract ImmutableList<String> identification();
abstract ImmutableList<String> replacement();
}
@AutoValue
abstract static class JekyllRefasterRuleCollectionDescription {
// XXX: Make this a derived property?
abstract String title();
abstract String name();
abstract SeverityLevel severity();
abstract ImmutableList<String> tags();
// XXX: The documentation could link to the original test code. Perhaps even with the correct
// line numbers. If we do this, we should do the same for individual rules.
abstract String source();
abstract ImmutableList<Rule> rules();
@AutoValue
abstract static class Rule {
abstract String name();
abstract SeverityLevel severity();
abstract ImmutableList<String> tags();
abstract String diff();
}
}
}

View File

@@ -0,0 +1,176 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static java.util.stream.Collectors.joining;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import java.net.URI;
import java.util.Optional;
import java.util.regex.Pattern;
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
import tech.picnic.errorprone.utils.SourceCode;
/**
* An {@link Extractor} that describes how to extract data from Refaster rule input and output test
* classes.
*/
// XXX: Drop this extractor if/when the Refaster test framework is reimplemented such that tests can
// be located alongside rules, rather than in two additional resource files as currently required by
// `RefasterRuleCollection`.
@Immutable
@AutoService(Extractor.class)
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
public final class RefasterRuleCollectionTestExtractor implements Extractor<RefasterTestCases> {
private static final Matcher<ClassTree> IS_REFASTER_RULE_COLLECTION_TEST_CASE =
isSubtypeOf("tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase");
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
private static final Pattern TEST_CLASS_FILE_NAME_PATTERN =
Pattern.compile(".*(Input|Output)\\.java");
private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("test(.*)");
private static final String LINE_SEPARATOR = "\n";
private static final Splitter LINE_SPLITTER = Splitter.on(LINE_SEPARATOR);
/** Instantiates a new {@link RefasterRuleCollectionTestExtractor} instance. */
public RefasterRuleCollectionTestExtractor() {}
@Override
public String identifier() {
return "refaster-rule-collection-test";
}
@Override
public Optional<RefasterTestCases> tryExtract(ClassTree tree, VisitorState state) {
if (!IS_REFASTER_RULE_COLLECTION_TEST_CASE.matches(tree, state)) {
return Optional.empty();
}
URI sourceFile = state.getPath().getCompilationUnit().getSourceFile().toUri();
return Optional.of(
RefasterTestCases.create(
sourceFile,
getRuleCollectionName(tree),
isInputFile(sourceFile),
getRefasterTestCases(tree, state)));
}
private static String getRuleCollectionName(ClassTree tree) {
String className = tree.getSimpleName().toString();
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
// aspects of `RefasterRuleCollectionTestCase` subtypes.
return tryExtractPatternGroup(className, TEST_CLASS_NAME_PATTERN)
.orElseThrow(
violation(
"Refaster rule collection test class name '%s' does not match '%s'",
className, TEST_CLASS_NAME_PATTERN));
}
private static boolean isInputFile(URI sourceFile) {
String path = sourceFile.getPath();
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
// aspects of `RefasterRuleCollectionTestCase` subtypes.
return "Input"
.equals(
tryExtractPatternGroup(path, TEST_CLASS_FILE_NAME_PATTERN)
.orElseThrow(
violation(
"Refaster rule collection test file name '%s' does not match '%s'",
path, TEST_CLASS_FILE_NAME_PATTERN)));
}
private static ImmutableList<RefasterTestCase> getRefasterTestCases(
ClassTree tree, VisitorState state) {
return tree.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.flatMap(m -> tryExtractRefasterTestCase(m, state).stream())
.collect(toImmutableList());
}
private static Optional<RefasterTestCase> tryExtractRefasterTestCase(
MethodTree method, VisitorState state) {
return tryExtractPatternGroup(method.getName().toString(), TEST_METHOD_NAME_PATTERN)
.map(name -> RefasterTestCase.create(name, getFormattedSource(method, state)));
}
/**
* Returns the source code for the specified method.
*
* @implNote This operation attempts to trim leading whitespace, such that the start and end of
* the method declaration are aligned. The implemented heuristic assumes that the code is
* formatted using Google Java Format.
*/
// XXX: Leading Javadoc and other comments are currently not extracted. Consider fixing this.
private static String getFormattedSource(MethodTree method, VisitorState state) {
String source = SourceCode.treeToString(method, state);
int finalNewline = source.lastIndexOf(LINE_SEPARATOR);
if (finalNewline < 0) {
return source;
}
int indentation = Math.max(0, source.lastIndexOf(' ') - finalNewline);
String prefixToStrip = " ".repeat(indentation);
return LINE_SPLITTER
.splitToStream(source)
.map(line -> line.startsWith(prefixToStrip) ? line.substring(indentation) : line)
.collect(joining(LINE_SEPARATOR));
}
private static Optional<String> tryExtractPatternGroup(String input, Pattern pattern) {
java.util.regex.Matcher matcher = pattern.matcher(input);
return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty();
}
@FormatMethod
private static Supplier<VerifyException> violation(String format, Object... args) {
return () -> new VerifyException(String.format(format, args));
}
@AutoValue
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases.class)
abstract static class RefasterTestCases {
static RefasterTestCases create(
URI source,
String ruleCollection,
boolean isInput,
ImmutableList<RefasterTestCase> testCases) {
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases(
source, ruleCollection, isInput, testCases);
}
abstract URI source();
abstract String ruleCollection();
abstract boolean isInput();
abstract ImmutableList<RefasterTestCase> testCases();
}
@AutoValue
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase.class)
abstract static class RefasterTestCase {
static RefasterTestCase create(String name, String content) {
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase(name, content);
}
abstract String name();
abstract String content();
}
}

View File

@@ -0,0 +1,27 @@
package tech.picnic.errorprone.documentation.models;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
/**
* Object containing all data related to a Refaster template collection. This is solely used for
* serialization.
*/
// XXX: This class is not yet used.
@AutoValue
@JsonDeserialize(as = AutoValue_RefasterTemplateCollectionData.class)
abstract class RefasterTemplateCollectionData {
static RefasterTemplateCollectionData create(
String name, String description, String link, ImmutableList<RefasterTemplateData> templates) {
return new AutoValue_RefasterTemplateCollectionData(name, description, link, templates);
}
abstract String name();
abstract String description();
abstract String link();
abstract ImmutableList<RefasterTemplateData> templates();
}

View File

@@ -0,0 +1,23 @@
package tech.picnic.errorprone.documentation.models;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.value.AutoValue;
import com.google.errorprone.BugPattern.SeverityLevel;
// XXX: This class is not yet used.
@AutoValue
@JsonDeserialize(as = AutoValue_RefasterTemplateData.class)
abstract class RefasterTemplateData {
static RefasterTemplateData create(
String name, String description, String link, SeverityLevel severityLevel) {
return new AutoValue_RefasterTemplateData(name, description, link, severityLevel);
}
abstract String name();
abstract String description();
abstract String link();
abstract SeverityLevel severityLevel();
}

View File

@@ -7,10 +7,10 @@ import java.net.URI;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCase;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.IdentificationTestEntry;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.ReplacementTestEntry;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCase;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
final class BugPatternTestExtractorTest {
@Test
@@ -269,11 +269,11 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"SingleFileCompilationTestHelperTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///SingleFileCompilationTestHelperTest.java"),
"SingleFileCompilationTestHelperTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"SingleFileCompilationTestHelperTest.TestChecker",
ImmutableList.of(
IdentificationTestEntry.create(
@@ -302,11 +302,11 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"SingleFileCompilationTestHelperWithSetArgsTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///SingleFileCompilationTestHelperWithSetArgsTest.java"),
"SingleFileCompilationTestHelperWithSetArgsTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"SingleFileCompilationTestHelperWithSetArgsTest.TestChecker",
ImmutableList.of(
IdentificationTestEntry.create(
@@ -335,11 +335,11 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"MultiFileCompilationTestHelperTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///MultiFileCompilationTestHelperTest.java"),
"MultiFileCompilationTestHelperTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"MultiFileCompilationTestHelperTest.TestChecker",
ImmutableList.of(
IdentificationTestEntry.create(
@@ -370,11 +370,11 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"SingleFileBugCheckerRefactoringTestHelperTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///SingleFileBugCheckerRefactoringTestHelperTest.java"),
"SingleFileBugCheckerRefactoringTestHelperTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"SingleFileBugCheckerRefactoringTestHelperTest.TestChecker",
ImmutableList.of(
ReplacementTestEntry.create(
@@ -408,12 +408,12 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
TestCases.create(
BugPatternTestCases.create(
URI.create(
"file:///SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java"),
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.TestChecker",
ImmutableList.of(
ReplacementTestEntry.create(
@@ -444,11 +444,11 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"MultiFileBugCheckerRefactoringTestHelperTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///MultiFileBugCheckerRefactoringTestHelperTest.java"),
"MultiFileBugCheckerRefactoringTestHelperTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"MultiFileBugCheckerRefactoringTestHelperTest.TestChecker",
ImmutableList.of(
ReplacementTestEntry.create(
@@ -484,16 +484,16 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"CompilationAndBugCheckerRefactoringTestHelpersTest",
TestCases.create(
BugPatternTestCases.create(
URI.create("file:///CompilationAndBugCheckerRefactoringTestHelpersTest.java"),
"CompilationAndBugCheckerRefactoringTestHelpersTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
ImmutableList.of(
IdentificationTestEntry.create(
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
TestCase.create(
BugPatternTestCase.create(
"CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker",
ImmutableList.of(
ReplacementTestEntry.create(
@@ -532,17 +532,17 @@ final class BugPatternTestExtractorTest {
verifyGeneratedFileContent(
outputDirectory,
"CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
TestCases.create(
BugPatternTestCases.create(
URI.create(
"file:///CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java"),
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest",
ImmutableList.of(
TestCase.create(
BugPatternTestCase.create(
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker",
ImmutableList.of(
IdentificationTestEntry.create(
"A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))),
TestCase.create(
BugPatternTestCase.create(
"pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker2",
ImmutableList.of(
ReplacementTestEntry.create(
@@ -550,9 +550,9 @@ final class BugPatternTestExtractorTest {
}
private static void verifyGeneratedFileContent(
Path outputDirectory, String testClass, TestCases expected) {
Path outputDirectory, String testClass, BugPatternTestCases expected) {
assertThat(outputDirectory.resolve(String.format("bugpattern-test-%s.json", testClass)))
.exists()
.returns(expected, path -> Json.read(path, TestCases.class));
.returns(expected, path -> Json.read(path, BugPatternTestCases.class));
}
}

View File

@@ -0,0 +1,13 @@
package tech.picnic.errorprone.documentation;
import java.io.IOException;
import org.junit.jupiter.api.Test;
// XXX: Implement tests.
final class JekyllCollectionGeneratorTest {
@Test
void foo() throws IOException {
JekyllCollectionGenerator.main(
new String[] {"/home/sschroevers/workspace/picnic/error-prone-support"});
}
}

View File

@@ -0,0 +1,166 @@
package tech.picnic.errorprone.documentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import java.net.URI;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase;
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
final class RefasterRuleCollectionTestExtractorTest {
@Test
void noRefasterRuleTest(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory, "NoRefasterRuleTest.java", "public final class NoRefasterRuleTest {}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void invalidTestClassName(@TempDir Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"InvalidTestClassNameInput.java",
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
"",
"final class InvalidTestClassName implements RefasterRuleCollectionTestCase {}"))
.cause()
.isInstanceOf(VerifyException.class)
.hasMessage(
"Refaster rule collection test class name 'InvalidTestClassName' does not match '(.*)Test'");
}
@Test
void invalidFileName(@TempDir Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"InvalidFileNameTest.java",
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
"",
"final class InvalidFileNameTest implements RefasterRuleCollectionTestCase {}"))
.cause()
.isInstanceOf(VerifyException.class)
.hasMessage(
"Refaster rule collection test file name '/InvalidFileNameTest.java' does not match '.*(Input|Output)\\.java'");
}
@Test
void emptyRefasterRuleCollectionTestInput(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"EmptyRefasterRuleCollectionTestInput.java",
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
"",
"final class EmptyRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {}");
verifyGeneratedFileContent(
outputDirectory,
"EmptyRefasterRuleCollectionTestInput",
RefasterTestCases.create(
URI.create("file:///EmptyRefasterRuleCollectionTestInput.java"),
"EmptyRefasterRuleCollection",
/* isInput= */ true,
ImmutableList.of()));
}
@Test
void singletonRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"SingletonRefasterRuleCollectionTestOutput.java",
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
"",
"final class SingletonRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
" int testMyRule() {",
" return 42;",
" }",
"}");
verifyGeneratedFileContent(
outputDirectory,
"SingletonRefasterRuleCollectionTestOutput",
RefasterTestCases.create(
URI.create("file:///SingletonRefasterRuleCollectionTestOutput.java"),
"SingletonRefasterRuleCollection",
/* isInput= */ false,
ImmutableList.of(
RefasterTestCase.create(
"MyRule",
"""
int testMyRule() {
return 42;
}"""))));
}
@Test
void complexRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"pkg/ComplexRefasterRuleCollectionTestInput.java",
"package pkg;",
"",
"import com.google.common.collect.ImmutableSet;",
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
"",
"final class ComplexRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
" private static final String IGNORED_CONSTANT = \"constant\";",
"",
" @Override",
" public ImmutableSet<Object> elidedTypesAndStaticImports() {",
" return ImmutableSet.of();",
" }",
"",
" /** Javadoc. */",
" String testFirstRule() {",
" return \"Don't panic\";",
" }",
"",
" // Comment.",
" String testSecondRule() {",
" return \"Carry a towel\";",
" }",
"",
" void testEmptyRule() {}",
"}");
verifyGeneratedFileContent(
outputDirectory,
"ComplexRefasterRuleCollectionTestInput",
RefasterTestCases.create(
URI.create("file:///pkg/ComplexRefasterRuleCollectionTestInput.java"),
"ComplexRefasterRuleCollection",
/* isInput= */ true,
ImmutableList.of(
RefasterTestCase.create(
"FirstRule",
"""
String testFirstRule() {
return "Don't panic";
}"""),
RefasterTestCase.create(
"SecondRule",
"""
String testSecondRule() {
return "Carry a towel";
}"""),
RefasterTestCase.create("EmptyRule", "void testEmptyRule() {}"))));
}
private static void verifyGeneratedFileContent(
Path outputDirectory, String testIdentifier, RefasterTestCases expected) {
assertThat(
outputDirectory.resolve(
String.format("refaster-rule-collection-test-%s.json", testIdentifier)))
.exists()
.returns(expected, path -> Json.read(path, RefasterTestCases.class));
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.18.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -67,6 +67,11 @@
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
@@ -287,6 +292,55 @@
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
</compilerArgs>
</configuration>
<executions>
<!-- The Refaster input/output test classes used by
`RefasterRuleCollection` are modelled as classpath
resources, and thus not subject to the default test
compilation step. These two custom compilation steps
serve two purposes:
- To provide early feedback in case of syntax errors.
- To enable the `DocumentationGenerator` compiler
plugin to extract documentation metadata from them.
Note that the input and output files must be compiled
separately and to distinct output directories, as they
define the same set of class names. -->
<!-- XXX: Drop these executions if/when the Refaster
test framework is reimplemented such that tests can be
located alongside rules, rather than in two additional
resource files. -->
<execution>
<id>compile-refaster-test-input</id>
<goals>
<goal>testCompile</goal>
</goals>
<phase>process-test-resources</phase>
<configuration>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
</compileSourceRoots>
<testIncludes>
<testInclude>**/*Input.java</testInclude>
</testIncludes>
<outputDirectory>${project.build.directory}/refaster-test-input</outputDirectory>
</configuration>
</execution>
<execution>
<id>compile-refaster-test-output</id>
<goals>
<goal>testCompile</goal>
</goals>
<phase>process-test-resources</phase>
<configuration>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
</compileSourceRoots>
<testIncludes>
<testInclude>**/*Output.java</testInclude>
</testIncludes>
<outputDirectory>${project.build.directory}/refaster-test-output</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>

View File

@@ -51,6 +51,7 @@ import tech.picnic.errorprone.utils.SourceCode;
// is effectively the identity operation.
// XXX: Also flag nullary instance method invocations that represent an identity conversion, such as
// `Boolean#booleanValue()`, `Byte#byteValue()` and friends.
// XXX: Also flag redundant round-trip conversions such as `path.toFile().toPath()`.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid or clarify identity conversions",

View File

@@ -18,14 +18,12 @@ 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.refaster.matchers.RequiresComputation;
import tech.picnic.errorprone.utils.SourceCode;
/**
@@ -36,12 +34,12 @@ import tech.picnic.errorprone.utils.SourceCode;
* 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.
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Review whether a `@Matcher` can be used to avoid this.
// XXX: Once the `MethodReferenceUsageCheck` bug checker 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 `@NotMatches(RequiresComputation.class)` constraint.
@AutoService(BugChecker.class)
@BugPattern(
summary =
@@ -51,16 +49,17 @@ import tech.picnic.errorprone.utils.SourceCode;
linkType = NONE,
severity = WARNING,
tags = PERFORMANCE)
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
public final class OptionalOrElseGet extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> REQUIRES_COMPUTATION = new RequiresComputation();
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() {}
/** Instantiates a new {@link OptionalOrElseGet} instance. */
public OptionalOrElseGet() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
@@ -69,7 +68,8 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
}
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
if (!REQUIRES_COMPUTATION.matches(argument, state)
|| REFASTER_METHOD.matches(argument, state)) {
return Description.NO_MATCH;
}
@@ -91,18 +91,6 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
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) {
@@ -118,7 +106,7 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
return Optional.empty();
}
if (requiresComputation(memberSelect.getExpression())) {
if (REQUIRES_COMPUTATION.matches(memberSelect.getExpression(), state)) {
return Optional.empty();
}

View File

@@ -24,8 +24,10 @@ 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;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
@@ -243,18 +245,77 @@ final class ComparatorRules {
}
}
/** Prefer {@link Collections#sort(List)} over more verbose alternatives. */
static final class CollectionsSort<T extends Comparable<? super T>> {
@BeforeTemplate
void before(List<T> collection) {
Collections.sort(collection, naturalOrder());
}
@AfterTemplate
void after(List<T> collection) {
Collections.sort(collection);
}
}
/** Prefer {@link Collections#min(Collection)} over more verbose alternatives. */
static final class CollectionsMin<T extends Comparable<? super T>> {
@BeforeTemplate
T before(Collection<T> collection) {
return Refaster.anyOf(
Collections.min(collection, naturalOrder()), Collections.max(collection, reverseOrder()));
}
@AfterTemplate
T after(Collection<T> collection) {
return Collections.min(collection);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class MinOfVarargs<T> {
static final class MinOfArray<S, T extends S> {
@BeforeTemplate
T before(@Repeated T value, Comparator<T> cmp) {
T before(T[] array, Comparator<S> cmp) {
return Arrays.stream(array).min(cmp).orElseThrow();
}
@AfterTemplate
T after(T[] array, Comparator<S> cmp) {
return Collections.min(Arrays.asList(array), cmp);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class CollectionsMinWithComparator<S, T extends S> {
@BeforeTemplate
T before(Collection<T> collection, Comparator<S> cmp) {
return collection.stream().min(cmp).orElseThrow();
}
@AfterTemplate
T after(Collection<T> collection, Comparator<S> cmp) {
return Collections.min(collection, cmp);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class MinOfVarargs<S, T extends S> {
@BeforeTemplate
T before(@Repeated T value, Comparator<S> cmp) {
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
T after(@Repeated T value, Comparator<S> cmp) {
return Collections.min(Arrays.asList(value), cmp);
}
}
@@ -310,18 +371,64 @@ final class ComparatorRules {
}
}
/** Prefer {@link Collections#max(Collection)} over more verbose alternatives. */
static final class CollectionsMax<T extends Comparable<? super T>> {
@BeforeTemplate
T before(Collection<T> collection) {
return Refaster.anyOf(
Collections.max(collection, naturalOrder()), Collections.min(collection, reverseOrder()));
}
@AfterTemplate
T after(Collection<T> collection) {
return Collections.max(collection);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class MaxOfVarargs<T> {
static final class MaxOfArray<S, T extends S> {
@BeforeTemplate
T before(@Repeated T value, Comparator<T> cmp) {
T before(T[] array, Comparator<S> cmp) {
return Arrays.stream(array).max(cmp).orElseThrow();
}
@AfterTemplate
T after(T[] array, Comparator<S> cmp) {
return Collections.max(Arrays.asList(array), cmp);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class CollectionsMaxWithComparator<S, T extends S> {
@BeforeTemplate
T before(Collection<T> collection, Comparator<S> cmp) {
return collection.stream().max(cmp).orElseThrow();
}
@AfterTemplate
T after(Collection<T> collection, Comparator<S> cmp) {
return Collections.max(collection, cmp);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class MaxOfVarargs<S, T extends S> {
@BeforeTemplate
T before(@Repeated T value, Comparator<S> cmp) {
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
T after(@Repeated T value, Comparator<S> cmp) {
return Collections.max(Arrays.asList(value), cmp);
}
}

View File

@@ -5,11 +5,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -18,6 +21,49 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
final class FileRules {
private FileRules() {}
/** Prefer the more idiomatic {@link Path#of(URI)} over {@link Paths#get(URI)}. */
static final class PathOfUri {
@BeforeTemplate
Path before(URI uri) {
return Paths.get(uri);
}
@AfterTemplate
Path after(URI uri) {
return Path.of(uri);
}
}
/**
* Prefer the more idiomatic {@link Path#of(String, String...)} over {@link Paths#get(String,
* String...)}.
*/
static final class PathOfString {
@BeforeTemplate
Path before(String first, @Repeated String more) {
return Paths.get(first, more);
}
@AfterTemplate
Path after(String first, @Repeated String more) {
return Path.of(first, more);
}
}
/** Avoid redundant conversions from {@link Path} to {@link File}. */
// XXX: Review whether a rule such as this one is better handled by the `IdentityConversion` rule.
static final class PathInstance {
@BeforeTemplate
Path before(Path path) {
return path.toFile().toPath();
}
@AfterTemplate
Path after(Path path) {
return path;
}
}
/** Prefer {@link Files#readString(Path, Charset)} over more contrived alternatives. */
static final class FilesReadStringWithCharset {
@BeforeTemplate
@@ -50,7 +96,11 @@ final class FileRules {
*/
static final class FilesCreateTempFileToFile {
@BeforeTemplate
@SuppressWarnings("java:S5443" /* This violation will be rewritten. */)
@SuppressWarnings({
"FilesCreateTempFileInCustomDirectoryToFile" /* This is a more specific template. */,
"java:S5443" /* This violation will be rewritten. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
File before(String prefix, String suffix) throws IOException {
return Refaster.anyOf(
File.createTempFile(prefix, suffix), File.createTempFile(prefix, suffix, null));
@@ -63,4 +113,20 @@ final class FileRules {
return Files.createTempFile(prefix, suffix).toFile();
}
}
/**
* Prefer {@link Files#createTempFile(Path, String, String, FileAttribute[])} over alternatives
* that create files with more liberal permissions.
*/
static final class FilesCreateTempFileInCustomDirectoryToFile {
@BeforeTemplate
File before(File directory, String prefix, String suffix) throws IOException {
return File.createTempFile(prefix, suffix, directory);
}
@AfterTemplate
File after(File directory, String prefix, String suffix) throws IOException {
return Files.createTempFile(directory.toPath(), prefix, suffix).toFile();
}
}
}

View File

@@ -20,7 +20,7 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
/** Refaster rules related to expressions dealing with {@link Optional}s. */
@OnlineDocumentation
@@ -255,24 +255,21 @@ final class OptionalRules {
}
/**
* Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the
* fallback value is not the result of a trivial computation.
* Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the
* fallback value does not require non-trivial computation.
*/
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Review whether a `@Matcher` can be used to avoid this.
// XXX: Once `MethodReferenceUsage` is "production ready", replace
// `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and
// reimplement the matcher accordingly).
static final class OptionalOrElseGet<T> {
// XXX: This rule is the counterpart to the `OptionalOrElseGet` bug checker. Once the
// `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be
// replaced with a Refaster rule.
static final class OptionalOrElse<T> {
@BeforeTemplate
T before(Optional<T> optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
return optional.orElse(value);
T before(Optional<T> optional, @NotMatches(RequiresComputation.class) T value) {
return optional.orElseGet(() -> value);
}
@AfterTemplate
T after(Optional<T> optional, T value) {
return optional.orElseGet(() -> value);
return optional.orElse(value);
}
}
@@ -373,7 +370,12 @@ final class OptionalRules {
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
static final class OptionalOrOtherOptional<T> {
@BeforeTemplate
@SuppressWarnings("NestedOptionals")
@SuppressWarnings({
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
"NestedOptionals" /* This violation will be rewritten. */,
"OptionalOrElse" /* Parameters represent expressions that may require computation. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
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.

View File

@@ -8,6 +8,8 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
@@ -76,6 +78,8 @@ final class PrimitiveRules {
}
/** Prefer {@link Math#toIntExact(long)} over the Guava alternative. */
// XXX: This rule changes the exception possibly thrown from `IllegalArgumentException` to
// `ArithmeticException`.
static final class LongToIntExact {
@BeforeTemplate
int before(long l) {
@@ -192,72 +196,8 @@ final class PrimitiveRules {
}
}
/** Prefer {@link Boolean#compare(boolean, boolean)} over the Guava alternative. */
static final class BooleanCompare {
@BeforeTemplate
int before(boolean a, boolean b) {
return Booleans.compare(a, b);
}
@AfterTemplate
int after(boolean a, boolean b) {
return Boolean.compare(a, b);
}
}
/** Prefer {@link Character#compare(char, char)} over the Guava alternative. */
static final class CharacterCompare {
@BeforeTemplate
int before(char a, char b) {
return Chars.compare(a, b);
}
@AfterTemplate
int after(char a, char b) {
return Character.compare(a, b);
}
}
/** Prefer {@link Short#compare(short, short)} over the Guava alternative. */
static final class ShortCompare {
@BeforeTemplate
int before(short a, short b) {
return Shorts.compare(a, b);
}
@AfterTemplate
int after(short a, short b) {
return Short.compare(a, b);
}
}
/** Prefer {@link Integer#compare(int, int)} over the Guava alternative. */
static final class IntegerCompare {
@BeforeTemplate
int before(int a, int b) {
return Ints.compare(a, b);
}
@AfterTemplate
int after(int a, int b) {
return Integer.compare(a, b);
}
}
/** Prefer {@link Long#compare(long, long)} over the Guava alternative. */
static final class LongCompare {
@BeforeTemplate
int before(long a, long b) {
return Longs.compare(a, b);
}
@AfterTemplate
int after(long a, long b) {
return Long.compare(a, b);
}
}
/** Prefer {@link Float#compare(float, float)} over the Guava alternative. */
// XXX: Drop this rule once https://github.com/google/guava/pull/7371 is released.
static final class FloatCompare {
@BeforeTemplate
int before(float a, float b) {
@@ -271,6 +211,7 @@ final class PrimitiveRules {
}
/** Prefer {@link Double#compare(double, double)} over the Guava alternative. */
// XXX: Drop this rule once https://github.com/google/guava/pull/7371 is released.
static final class DoubleCompare {
@BeforeTemplate
int before(double a, double b) {
@@ -442,4 +383,205 @@ final class PrimitiveRules {
return Long.signum(l) == -1;
}
}
/** Prefer JDK's {@link Integer#compareUnsigned(int, int)} over third-party alternatives. */
static final class IntegerCompareUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.compare(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.compareUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#compareUnsigned(long, long)} over third-party alternatives. */
static final class LongCompareUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.compare(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.compareUnsigned(x, y);
}
}
/** Prefer JDK's {@link Integer#divideUnsigned(int, int)} over third-party alternatives. */
static final class IntegerDivideUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.divide(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.divideUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#divideUnsigned(long, long)} over third-party alternatives. */
static final class LongDivideUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.divide(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.divideUnsigned(x, y);
}
}
/** Prefer JDK's {@link Integer#remainderUnsigned(int, int)} over third-party alternatives. */
static final class IntegerRemainderUnsigned {
@BeforeTemplate
int before(int x, int y) {
return UnsignedInts.remainder(x, y);
}
@AfterTemplate
int after(int x, int y) {
return Integer.remainderUnsigned(x, y);
}
}
/** Prefer JDK's {@link Long#remainderUnsigned(long, long)} over third-party alternatives. */
static final class LongRemainderUnsigned {
@BeforeTemplate
long before(long x, long y) {
return UnsignedLongs.remainder(x, y);
}
@AfterTemplate
long after(long x, long y) {
return Long.remainderUnsigned(x, y);
}
}
/**
* Prefer JDK's {@link Integer#parseUnsignedInt(String)} over third-party or more verbose
* alternatives.
*/
static final class IntegerParseUnsignedInt {
@BeforeTemplate
int before(String string) {
return Refaster.anyOf(
UnsignedInts.parseUnsignedInt(string), Integer.parseUnsignedInt(string, 10));
}
@AfterTemplate
int after(String string) {
return Integer.parseUnsignedInt(string);
}
}
/**
* Prefer JDK's {@link Long#parseUnsignedLong(String)} over third-party or more verbose
* alternatives.
*/
static final class LongParseUnsignedLong {
@BeforeTemplate
long before(String string) {
return Refaster.anyOf(
UnsignedLongs.parseUnsignedLong(string), Long.parseUnsignedLong(string, 10));
}
@AfterTemplate
long after(String string) {
return Long.parseUnsignedLong(string);
}
}
/** Prefer JDK's {@link Integer#parseUnsignedInt(String, int)} over third-party alternatives. */
static final class IntegerParseUnsignedIntWithRadix {
@BeforeTemplate
int before(String string, int radix) {
return UnsignedInts.parseUnsignedInt(string, radix);
}
@AfterTemplate
int after(String string, int radix) {
return Integer.parseUnsignedInt(string, radix);
}
}
/** Prefer JDK's {@link Long#parseUnsignedLong(String, int)} over third-party alternatives. */
static final class LongParseUnsignedLongWithRadix {
@BeforeTemplate
long before(String string, int radix) {
return UnsignedLongs.parseUnsignedLong(string, radix);
}
@AfterTemplate
long after(String string, int radix) {
return Long.parseUnsignedLong(string, radix);
}
}
/**
* Prefer JDK's {@link Integer#toUnsignedString(int)} over third-party or more verbose
* alternatives.
*/
static final class IntegerToUnsignedString {
@BeforeTemplate
String before(int i) {
return Refaster.anyOf(UnsignedInts.toString(i), Integer.toUnsignedString(i, 10));
}
@AfterTemplate
String after(int i) {
return Integer.toUnsignedString(i);
}
}
/**
* Prefer JDK's {@link Long#toUnsignedString(long)} over third-party or more verbose alternatives.
*/
static final class LongToUnsignedString {
@BeforeTemplate
String before(long i) {
return Refaster.anyOf(UnsignedLongs.toString(i), Long.toUnsignedString(i, 10));
}
@AfterTemplate
String after(long i) {
return Long.toUnsignedString(i);
}
}
/**
* Prefer JDK's {@link Integer#toUnsignedString(int,int)} over third-party or more verbose
* alternatives.
*/
static final class IntegerToUnsignedStringWithRadix {
@BeforeTemplate
String before(int i, int radix) {
return UnsignedInts.toString(i, radix);
}
@AfterTemplate
String after(int i, int radix) {
return Integer.toUnsignedString(i, radix);
}
}
/**
* Prefer JDK's {@link Long#toUnsignedString(long,int)} over third-party or more verbose
* alternatives.
*/
static final class LongToUnsignedStringWithRadix {
@BeforeTemplate
String before(long i, int radix) {
return UnsignedLongs.toString(i, radix);
}
@AfterTemplate
String after(long i, int radix) {
return Long.toUnsignedString(i, radix);
}
}
}

View File

@@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -1956,6 +1957,22 @@ final class ReactorRules {
}
}
/**
* Don't propagate {@link Mono} cancellations to an upstream cache value computation, as
* completion of such computations may benefit concurrent or subsequent cache usages.
*/
static final class MonoFromFutureAsyncLoadingCacheGet<K, V> {
@BeforeTemplate
Mono<V> before(AsyncLoadingCache<K, V> cache, K key) {
return Mono.fromFuture(() -> cache.get(key));
}
@AfterTemplate
Mono<V> after(AsyncLoadingCache<K, V> cache, K key) {
return Mono.fromFuture(() -> cache.get(key), /* suppressCancel= */ true);
}
}
/**
* Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former
* yields a {@link Flux} that is more likely to behave as expected when subscribed to more than

View File

@@ -5,14 +5,15 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class OptionalOrElseTest {
final class OptionalOrElseGetTest {
@Test
void identification() {
CompilationTestHelper.newInstance(OptionalOrElse.class, getClass())
CompilationTestHelper.newInstance(OptionalOrElseGet.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",
"import java.util.Optional;",
"import java.util.function.Supplier;",
"",
"class A {",
" private final Optional<Object> optional = Optional.empty();",
@@ -27,6 +28,7 @@ final class OptionalOrElseTest {
" optional.orElse(string);",
" optional.orElse(this.string);",
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
"",
" // BUG: Diagnostic contains:",
" Optional.empty().orElse(string + \"constant\");",
@@ -67,7 +69,7 @@ final class OptionalOrElseTest {
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElse.class, getClass())
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElseGet.class, getClass())
.addInputLines(
"A.java",
"import java.util.Optional;",

View File

@@ -107,6 +107,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>reverseOrder().compare("baz", "qux"));
}
void testCollectionsSort() {
Collections.sort(ImmutableList.of("foo", "bar"), naturalOrder());
}
ImmutableSet<String> testCollectionsMin() {
return ImmutableSet.of(
Collections.min(ImmutableList.of("foo"), naturalOrder()),
Collections.max(ImmutableList.of("bar"), reverseOrder()));
}
String testMinOfArray() {
return Arrays.stream(new String[0]).min(naturalOrder()).orElseThrow();
}
String testCollectionsMinWithComparator() {
return ImmutableSet.of("foo", "bar").stream().min(naturalOrder()).orElseThrow();
}
int testMinOfVarargs() {
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
}
@@ -135,6 +153,20 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
ImmutableSet<String> testCollectionsMax() {
return ImmutableSet.of(
Collections.max(ImmutableList.of("foo"), naturalOrder()),
Collections.min(ImmutableList.of("bar"), reverseOrder()));
}
String testMaxOfArray() {
return Arrays.stream(new String[0]).max(naturalOrder()).orElseThrow();
}
String testCollectionsMaxWithComparator() {
return ImmutableSet.of("foo", "bar").stream().max(naturalOrder()).orElseThrow();
}
int testMaxOfVarargs() {
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
}

View File

@@ -98,6 +98,23 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
}
void testCollectionsSort() {
Collections.sort(ImmutableList.of("foo", "bar"));
}
ImmutableSet<String> testCollectionsMin() {
return ImmutableSet.of(
Collections.min(ImmutableList.of("foo")), Collections.min(ImmutableList.of("bar")));
}
String testMinOfArray() {
return Collections.min(Arrays.asList(new String[0]), naturalOrder());
}
String testCollectionsMinWithComparator() {
return Collections.min(ImmutableSet.of("foo", "bar"), naturalOrder());
}
int testMinOfVarargs() {
return Collections.min(Arrays.asList(1, 2), naturalOrder());
}
@@ -126,6 +143,19 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparators.min("a", "b", (a, b) -> 1));
}
ImmutableSet<String> testCollectionsMax() {
return ImmutableSet.of(
Collections.max(ImmutableList.of("foo")), Collections.max(ImmutableList.of("bar")));
}
String testMaxOfArray() {
return Collections.max(Arrays.asList(new String[0]), naturalOrder());
}
String testCollectionsMaxWithComparator() {
return Collections.max(ImmutableSet.of("foo", "bar"), naturalOrder());
}
int testMaxOfVarargs() {
return Collections.max(Arrays.asList(1, 2), naturalOrder());
}

View File

@@ -3,12 +3,26 @@ package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class FileRulesTest implements RefasterRuleCollectionTestCase {
Path testPathOfUri() {
return Paths.get(URI.create("foo"));
}
ImmutableSet<Path> testPathOfString() {
return ImmutableSet.of(Paths.get("foo"), Paths.get("bar", "baz", "qux"));
}
Path testPathInstance() {
return Path.of("foo").toFile().toPath();
}
String testFilesReadStringWithCharset() throws IOException {
return new String(Files.readAllBytes(Paths.get("foo")), StandardCharsets.ISO_8859_1);
}
@@ -21,4 +35,8 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
File.createTempFile("foo", "bar"), File.createTempFile("baz", "qux", null));
}
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
return File.createTempFile("foo", "bar", new File("baz"));
}
}

View File

@@ -3,12 +3,26 @@ package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class FileRulesTest implements RefasterRuleCollectionTestCase {
Path testPathOfUri() {
return Path.of(URI.create("foo"));
}
ImmutableSet<Path> testPathOfString() {
return ImmutableSet.of(Path.of("foo"), Path.of("bar", "baz", "qux"));
}
Path testPathInstance() {
return Path.of("foo");
}
String testFilesReadStringWithCharset() throws IOException {
return Files.readString(Paths.get("foo"), StandardCharsets.ISO_8859_1);
}
@@ -21,4 +35,8 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Files.createTempFile("foo", "bar").toFile(), Files.createTempFile("baz", "qux").toFile());
}
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
return Files.createTempFile(new File("baz").toPath(), "foo", "bar").toFile();
}
}

View File

@@ -26,6 +26,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
Stream.<Integer>empty().collect(toImmutableMultiset()));
}
@SuppressWarnings("unchecked")
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
return ImmutableMultiset.of(
ImmutableList.of(1).stream().collect(toImmutableMultiset()),

View File

@@ -24,6 +24,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
return ImmutableMultiset.of(ImmutableMultiset.of(), ImmutableMultiset.of());
}
@SuppressWarnings("unchecked")
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
return ImmutableMultiset.of(
ImmutableMultiset.copyOf(ImmutableList.of(1)),

View File

@@ -37,6 +37,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
Stream.<Integer>empty().collect(toImmutableSortedMultiset(naturalOrder())));
}
@SuppressWarnings("unchecked")
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
return ImmutableMultiset.of(
ImmutableSortedMultiset.copyOf(naturalOrder(), ImmutableList.of(1)),

View File

@@ -35,6 +35,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
return ImmutableMultiset.of(ImmutableSortedMultiset.of(), ImmutableSortedMultiset.of());
}
@SuppressWarnings("unchecked")
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
return ImmutableMultiset.of(
ImmutableSortedMultiset.copyOf(ImmutableList.of(1)),

View File

@@ -83,11 +83,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of("foo").orElseGet(() -> Optional.of("bar").orElseThrow());
}
ImmutableSet<String> testOptionalOrElseGet() {
ImmutableSet<String> testOptionalOrElse() {
return ImmutableSet.of(
Optional.of("foo").orElse("bar"),
Optional.of("baz").orElse(toString()),
Optional.of("qux").orElse(String.valueOf(true)));
Optional.of("foo").orElseGet(() -> "bar"), Optional.of("baz").orElseGet(() -> toString()));
}
ImmutableSet<Object> testStreamFlatMapOptional() {

View File

@@ -80,11 +80,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return Optional.of("foo").or(() -> Optional.of("bar")).orElseThrow();
}
ImmutableSet<String> testOptionalOrElseGet() {
ImmutableSet<String> testOptionalOrElse() {
return ImmutableSet.of(
Optional.of("foo").orElse("bar"),
Optional.of("baz").orElse(toString()),
Optional.of("qux").orElseGet(() -> String.valueOf(true)));
Optional.of("foo").orElse("bar"), Optional.of("baz").orElseGet(() -> toString()));
}
ImmutableSet<Object> testStreamFlatMapOptional() {

View File

@@ -9,6 +9,8 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
Floats.class,
Ints.class,
Longs.class,
Shorts.class);
Shorts.class,
UnsignedInts.class,
UnsignedLongs.class);
}
ImmutableSet<Boolean> testLessThan() {
@@ -105,26 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return Doubles.hashCode(1);
}
int testBooleanCompare() {
return Booleans.compare(false, true);
}
int testCharacterCompare() {
return Chars.compare('a', 'b');
}
int testShortCompare() {
return Shorts.compare((short) 1, (short) 2);
}
int testIntegerCompare() {
return Ints.compare(1, 2);
}
int testLongCompare() {
return Longs.compare(1, 2);
}
int testFloatCompare() {
return Floats.compare(1, 2);
}
@@ -190,4 +174,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Long.signum(1L) < 0, Long.signum(2L) <= -1, Long.signum(3L) >= 0, Long.signum(4L) > -1);
}
int testIntegerCompareUnsigned() {
return UnsignedInts.compare(1, 2);
}
long testLongCompareUnsigned() {
return UnsignedLongs.compare(1, 2);
}
int testIntegerDivideUnsigned() {
return UnsignedInts.divide(1, 2);
}
long testLongDivideUnsigned() {
return UnsignedLongs.divide(1, 2);
}
int testIntegerRemainderUnsigned() {
return UnsignedInts.remainder(1, 2);
}
long testLongRemainderUnsigned() {
return UnsignedLongs.remainder(1, 2);
}
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
return ImmutableSet.of(UnsignedInts.parseUnsignedInt("1"), Integer.parseUnsignedInt("2", 10));
}
ImmutableSet<Long> testLongParseUnsignedLong() {
return ImmutableSet.of(UnsignedLongs.parseUnsignedLong("1"), Long.parseUnsignedLong("2", 10));
}
int testIntegerParseUnsignedIntWithRadix() {
return UnsignedInts.parseUnsignedInt("1", 2);
}
long testLongParseUnsignedLongWithRadix() {
return UnsignedLongs.parseUnsignedLong("1", 2);
}
ImmutableSet<String> testIntegerToUnsignedString() {
return ImmutableSet.of(UnsignedInts.toString(1), Integer.toUnsignedString(2, 10));
}
ImmutableSet<String> testLongToUnsignedString() {
return ImmutableSet.of(UnsignedLongs.toString(1), Long.toUnsignedString(2, 10));
}
String testIntegerToUnsignedStringWithRadix() {
return UnsignedInts.toString(1, 2);
}
String testLongToUnsignedStringWithRadix() {
return UnsignedLongs.toString(1, 2);
}
}

View File

@@ -9,6 +9,8 @@ import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import com.google.common.primitives.UnsignedInts;
import com.google.common.primitives.UnsignedLongs;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
Floats.class,
Ints.class,
Longs.class,
Shorts.class);
Shorts.class,
UnsignedInts.class,
UnsignedLongs.class);
}
ImmutableSet<Boolean> testLessThan() {
@@ -105,26 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return Double.hashCode(1);
}
int testBooleanCompare() {
return Boolean.compare(false, true);
}
int testCharacterCompare() {
return Character.compare('a', 'b');
}
int testShortCompare() {
return Short.compare((short) 1, (short) 2);
}
int testIntegerCompare() {
return Integer.compare(1, 2);
}
int testLongCompare() {
return Long.compare(1, 2);
}
int testFloatCompare() {
return Float.compare(1, 2);
}
@@ -190,4 +174,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(
Long.signum(1L) == -1, Long.signum(2L) == -1, Long.signum(3L) != -1, Long.signum(4L) != -1);
}
int testIntegerCompareUnsigned() {
return Integer.compareUnsigned(1, 2);
}
long testLongCompareUnsigned() {
return Long.compareUnsigned(1, 2);
}
int testIntegerDivideUnsigned() {
return Integer.divideUnsigned(1, 2);
}
long testLongDivideUnsigned() {
return Long.divideUnsigned(1, 2);
}
int testIntegerRemainderUnsigned() {
return Integer.remainderUnsigned(1, 2);
}
long testLongRemainderUnsigned() {
return Long.remainderUnsigned(1, 2);
}
ImmutableSet<Integer> testIntegerParseUnsignedInt() {
return ImmutableSet.of(Integer.parseUnsignedInt("1"), Integer.parseUnsignedInt("2"));
}
ImmutableSet<Long> testLongParseUnsignedLong() {
return ImmutableSet.of(Long.parseUnsignedLong("1"), Long.parseUnsignedLong("2"));
}
int testIntegerParseUnsignedIntWithRadix() {
return Integer.parseUnsignedInt("1", 2);
}
long testLongParseUnsignedLongWithRadix() {
return Long.parseUnsignedLong("1", 2);
}
ImmutableSet<String> testIntegerToUnsignedString() {
return ImmutableSet.of(Integer.toUnsignedString(1), Integer.toUnsignedString(2));
}
ImmutableSet<String> testLongToUnsignedString() {
return ImmutableSet.of(Long.toUnsignedString(1), Long.toUnsignedString(2));
}
String testIntegerToUnsignedStringWithRadix() {
return Integer.toUnsignedString(1, 2);
}
String testLongToUnsignedStringWithRadix() {
return Long.toUnsignedString(1, 2);
}
}

View File

@@ -10,6 +10,7 @@ import static java.util.stream.Collectors.minBy;
import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -664,6 +665,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.fromFuture(CompletableFuture.completedFuture(null), true);
}
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0));
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(Stream.of(1));
}

View File

@@ -12,6 +12,7 @@ import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -644,6 +645,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true);
}
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0), true);
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(() -> Stream.of(1));
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.18.1-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.18.0</version>
<version>0.18.1-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.18.0</version>
<version>0.18.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-utils</artifactId>

View File

@@ -1,27 +0,0 @@
#!/usr/bin/env bash
set -e -u -o pipefail
REPOSITORY_ROOT="$(git rev-parse --show-toplevel)"
WEBSITE_ROOT="${REPOSITORY_ROOT}/website"
generate_homepage() {
local homepage="${WEBSITE_ROOT}/index.md"
echo "Generating ${homepage}..."
cat - "${REPOSITORY_ROOT}/README.md" > "${homepage}" << EOF
---
layout: default
title: Home
nav_order: 1
---
EOF
local macos_compat=""
[[ "${OSTYPE}" == "darwin"* ]] && macos_compat="yes"
sed -i ${macos_compat:+".bak"} 's/src="website\//src="/g' "${homepage}"
sed -i ${macos_compat:+".bak"} 's/srcset="website\//srcset="/g' "${homepage}"
}
# Generate the website.
generate_homepage

75
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.18.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -52,7 +52,7 @@
<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.18.0</tag>
<tag>HEAD</tag>
<url>https://github.com/PicnicSupermarket/error-prone-support</url>
</scm>
<issueManagement>
@@ -148,7 +148,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-08-11T13:04:41Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2024-08-11T13:05:54Z</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 +208,16 @@
<version.auto-value>1.11.0</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.30.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.25</version.error-prone-slf4j>
<version.error-prone-orig>2.32.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.28</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.12.0</version.mockito>
<version.maven>3.9.9</version.maven>
<version.mockito>5.13.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.11.1</version.nullaway>
<version.nullaway>0.11.2</version.nullaway>
<version.pitest-git>1.1.4</version.pitest-git>
<version.rewrite-templating>1.12.3</version.rewrite-templating>
<version.rewrite-templating>1.14.1</version.rewrite-templating>
<version.surefire>3.2.3</version.surefire>
</properties>
@@ -300,6 +300,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
@@ -338,7 +343,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>33.2.1-jre</version>
<version>33.3.0-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -357,10 +362,15 @@
<artifactId>nullaway</artifactId>
<version>${version.nullaway}</version>
</dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2023.0.8</version>
<version>2023.0.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -377,7 +387,7 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.22</version>
<version>2.2.23</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -412,7 +422,7 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.18</version>
<version>1.15.1</version>
</dependency>
<!-- Specified so that Renovate will file Maven upgrade PRs, which
subsequently will cause `maven-enforcer-plugin` to require that
@@ -437,7 +447,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.46.0</version>
<version>3.47.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -457,7 +467,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.3</version>
<version>5.11.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -471,7 +481,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>5.1.3</version>
<version>5.1.4</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
@@ -481,7 +491,7 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.16.0</version>
<version>2.18.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -495,19 +505,19 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.1.11</version>
<version>6.1.12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>3.3.2</version>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.3.1</version>
<version>6.3.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -562,7 +572,7 @@
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<version>2.22.1</version>
<version>2.24</version>
<configuration>
<additionalSourceDirectories>
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
@@ -648,7 +658,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.4.0</version>
<version>3.5.0</version>
<configuration>
<checkstyleRules>
<!-- We only enable rules that are not enforced by
@@ -897,7 +907,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.17.0</version>
<version>10.18.1</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -1001,7 +1011,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.7.1</version>
<version>3.8.0</version>
<configuration>
<!-- XXX: Drop `ignoreAllNonTestScoped` once
https://issues.apache.org/jira/browse/MNG-6058 is
@@ -1016,7 +1026,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
<version>3.1.3</version>
<configuration>
<retryFailedDeploymentCount>3</retryFailedDeploymentCount>
</configuration>
@@ -1109,7 +1119,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.4</version>
<version>3.2.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
@@ -1122,7 +1132,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
<version>3.1.3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -1188,7 +1198,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.8.0</version>
<version>3.10.0</version>
<configuration>
<additionalJOptions>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</additionalJOption>
@@ -1258,7 +1268,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
<version>3.5.0</version>
<configuration>
<includes>
<include>**/*Test.java</include>
@@ -1469,7 +1479,7 @@
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
<version>1.11.0</version>
<version>1.13.0</version>
<configuration>
<failOnError>false</failOnError>
<release>false</release>
@@ -1818,6 +1828,11 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.4.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refaster.matchers;
import com.google.errorprone.VisitorState;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
@@ -9,34 +10,19 @@ import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
/** A matcher of expressions that likely require little to no computation. */
public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree> {
/** A matcher of expressions that may a non-trivial amount of computation. */
public final class RequiresComputation implements Matcher<ExpressionTree> {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link IsLikelyTrivialComputation} instance. */
public IsLikelyTrivialComputation() {}
/** Instantiates a new {@link RequiresComputation} instance. */
public RequiresComputation() {}
@Override
public boolean matches(ExpressionTree expressionTree, VisitorState state) {
if (expressionTree instanceof MethodInvocationTree methodInvocation) {
// XXX: Method invocations are generally *not* trivial computations, but we make an exception
// for nullary method invocations on the result of a trivial computation. This exception
// allows this `Matcher` to by the `OptionalOrElseGet` Refaster rule, such that it does not
// suggest the introduction of lambda expressions that are better expressed as method
// references. Once the `MethodReferenceUsage` bug checker is production-ready, this exception
// should be removed. (But at that point, instead defining a `RequiresComputation` matcher may
// be more appropriate.)
if (methodInvocation.getArguments().isEmpty()
&& matches(methodInvocation.getMethodSelect())) {
return true;
}
}
return matches(expressionTree);
}
@@ -44,11 +30,11 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
// Depending on feedback such trees may be matched in the future.
private static boolean matches(ExpressionTree expressionTree) {
if (expressionTree instanceof ArrayAccessTree arrayAccess) {
return matches(arrayAccess.getExpression()) && matches(arrayAccess.getIndex());
return matches(arrayAccess.getExpression()) || matches(arrayAccess.getIndex());
}
if (expressionTree instanceof LiteralTree) {
return true;
return false;
}
if (expressionTree instanceof LambdaExpressionTree) {
@@ -56,11 +42,14 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
* Lambda expressions encapsulate computations, but their definition does not involve
* significant computation.
*/
return true;
return false;
}
if (expressionTree instanceof IdentifierTree) {
return true;
// XXX: Generally identifiers don't by themselves represent a computation, though they may be
// a stand-in for one if they are a Refaster template method argument. Can we identify such
// cases, also when the `Matcher` is invoked by Refaster?
return false;
}
if (expressionTree instanceof MemberReferenceTree memberReference) {
@@ -80,11 +69,11 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
}
if (expressionTree instanceof UnaryTree unary) {
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement are not
// trivial.
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement
// represent non-trivial computations.
return matches(unary.getExpression());
}
return false;
return ASTHelpers.constValue(expressionTree) == null;
}
}

View File

@@ -8,54 +8,69 @@ import com.google.errorprone.bugpatterns.BugChecker;
import com.sun.source.tree.ReturnTree;
import org.junit.jupiter.api.Test;
final class IsLikelyTrivialComputationTest {
final class RequiresComputationTest {
@Test
void matches() {
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import java.io.OutputStream;",
"import java.util.Comparator;",
"import java.util.function.Predicate;",
"",
"class A {",
" String negative1() {",
" return String.valueOf(1);",
" int negative1() {",
" int[] arr = new int[0];",
" return arr[0];",
" }",
"",
" String negative2() {",
" return toString().toString();",
" return null;",
" }",
"",
" String negative3() {",
" return \"foo\" + toString();",
" boolean negative3() {",
" return false;",
" }",
"",
" byte negative4() {",
" return \"foo\".getBytes()[0];",
" int negative4() {",
" return 0;",
" }",
"",
" int negative5() {",
" int[] arr = new int[0];",
" return arr[hashCode()];",
" String negative5() {",
" return \"foo\" + \"bar\";",
" }",
"",
" int negative6() {",
" return 1 * 2;",
" Predicate<String> negative6() {",
" return v -> \"foo\".equals(v);",
" }",
"",
" Predicate<String> negative7() {",
" return toString()::equals;",
" A negative7() {",
" return this;",
" }",
"",
" String negative8() {",
" return (toString());",
" Predicate<String> negative8() {",
" return \"foo\"::equals;",
" }",
"",
" Object negative9() {",
" return (Object) toString();",
" OutputStream negative9() {",
" return System.out;",
" }",
"",
" int negative10() {",
" return -hashCode();",
" A negative10() {",
" return (this);",
" }",
"",
" Object negative11() {",
" return (Object) this;",
" }",
"",
" boolean negative12() {",
" boolean[] arr = new boolean[0];",
" return !arr[0];",
" }",
"",
" String negative13() {",
" return \"foo\" + 0;",
" }",
"",
" String positive1() {",
@@ -68,68 +83,63 @@ final class IsLikelyTrivialComputationTest {
" return this.toString();",
" }",
"",
" int positive3() {",
" int[] arr = new int[0];",
" String positive3() {",
" // BUG: Diagnostic contains:",
" return arr[0];",
" return String.valueOf(1);",
" }",
"",
" String positive4() {",
" // BUG: Diagnostic contains:",
" return null;",
" return toString().toString();",
" }",
"",
" boolean positive5() {",
" String positive5() {",
" // BUG: Diagnostic contains:",
" return false;",
" return \"foo\" + toString();",
" }",
"",
" int positive6() {",
" byte positive6() {",
" // BUG: Diagnostic contains:",
" return 0;",
" return \"foo\".getBytes()[0];",
" }",
"",
" String positive7() {",
" int positive7() {",
" int[] arr = new int[0];",
" // BUG: Diagnostic contains:",
" return \"foo\" + \"bar\";",
" return arr[hashCode()];",
" }",
"",
" Predicate<String> positive8() {",
" // BUG: Diagnostic contains:",
" return v -> \"foo\".equals(v);",
" return toString()::equals;",
" }",
"",
" A positive9() {",
" Comparator<String> positive9() {",
" // BUG: Diagnostic contains:",
" return this;",
" return toString().CASE_INSENSITIVE_ORDER;",
" }",
"",
" Predicate<String> positive10() {",
" String positive10() {",
" // BUG: Diagnostic contains:",
" return \"foo\"::equals;",
" return (toString());",
" }",
"",
" A positive11() {",
" Object positive11() {",
" // BUG: Diagnostic contains:",
" return (this);",
" return (Object) toString();",
" }",
"",
" Object positive12() {",
" int positive12() {",
" // BUG: Diagnostic contains:",
" return (Object) this;",
" }",
"",
" boolean positive13() {",
" // BUG: Diagnostic contains:",
" return !false;",
" return -hashCode();",
" }",
"}")
.doTest();
}
/** A {@link BugChecker} that simply delegates to {@link IsLikelyTrivialComputation}. */
/** A {@link BugChecker} that simply delegates to {@link RequiresComputation}. */
@BugPattern(
summary = "Flags return statement expressions matched by `IsLikelyTrivialComputation`",
summary = "Flags return statement expressions matched by `RequiresComputation`",
severity = ERROR)
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
private static final long serialVersionUID = 1L;
@@ -141,7 +151,7 @@ final class IsLikelyTrivialComputationTest {
super(
(expressionTree, state) ->
state.getPath().getParentPath().getLeaf() instanceof ReturnTree
&& new IsLikelyTrivialComputation().matches(expressionTree, state));
&& new RequiresComputation().matches(expressionTree, state));
}
}
}

View File

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

View File

@@ -61,6 +61,9 @@ import tech.picnic.errorprone.refaster.runner.Refaster;
// XXX: This check currently only validates that one `Refaster.anyOf` branch in one
// `@BeforeTemplate` method is covered by a test. Review how we can make sure that _all_
// `@BeforeTemplate` methods and `Refaster.anyOf` branches are covered.
// XXX: Look into replacing this setup with another that allows test cases to be co-located
// with/nested within the rules. This way any rule change only requires modifications in a single
// place, rather than in three.
@BugPattern(summary = "Exercises a Refaster rule collection", linkType = NONE, severity = ERROR)
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */)
public final class RefasterRuleCollection extends BugChecker implements CompilationUnitTreeMatcher {

7
website/.gitignore vendored
View File

@@ -1,12 +1,13 @@
# Generated by Bundler and Jekyll.
.bundle/
Gemfile.lock
.jekyll-cache/
.jekyll-metadata
.sass-cache/
_site/
vendor/
# Generated by `../generate-docs.sh`.
*.bak
# XXX: Update generator name when it's renamed.
# Generated by `JekyllCollectionGenerator`.
index.md
_bugpatterns/
_refasterrules/

View File

@@ -1,8 +1,15 @@
ruby File.read(".ruby-version").strip
source "https://rubygems.org"
gem "html-proofer", "4.4.1"
gem "jekyll", "4.2.2"
gem "jekyll-sitemap", "1.4"
gem "just-the-docs", "0.4.0.rc2"
gem "webrick", "1.7"
group :jekyll_site_dependencies do
gem "jekyll", "4.3.2"
gem "jekyll-sitemap", "1.4.0"
gem "just-the-docs", "0.6.2"
gem "rake", "13.0.6"
gem "webrick", "1.8.1"
end
group :website_validation_dependencies do
gem "html-proofer", "5.0.8"
end

133
website/Gemfile.lock Normal file
View File

@@ -0,0 +1,133 @@
GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
afm (0.2.2)
async (2.6.4)
console (~> 1.10)
fiber-annotation
io-event (~> 1.1)
timers (~> 4.1)
colorator (1.1.0)
concurrent-ruby (1.2.2)
console (1.23.2)
fiber-annotation
fiber-local
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
ethon (0.16.0)
ffi (>= 1.15.0)
eventmachine (1.2.7)
ffi (1.16.3)
fiber-annotation (0.2.0)
fiber-local (1.0.0)
forwardable-extended (2.6.0)
google-protobuf (3.24.4-x86_64-linux)
hashery (2.1.2)
html-proofer (5.0.8)
addressable (~> 2.3)
async (~> 2.1)
nokogiri (~> 1.13)
pdf-reader (~> 2.11)
rainbow (~> 3.0)
typhoeus (~> 1.3)
yell (~> 2.0)
zeitwerk (~> 2.5)
http_parser.rb (0.8.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
io-event (1.3.2)
jekyll (4.3.2)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
i18n (~> 1.0)
jekyll-sass-converter (>= 2.0, < 4.0)
jekyll-watch (~> 2.0)
kramdown (~> 2.3, >= 2.3.1)
kramdown-parser-gfm (~> 1.0)
liquid (~> 4.0)
mercenary (>= 0.3.6, < 0.5)
pathutil (~> 0.9)
rouge (>= 3.0, < 5.0)
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-sass-converter (3.0.0)
sass-embedded (~> 1.54)
jekyll-seo-tag (2.8.0)
jekyll (>= 3.8, < 5.0)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
just-the-docs (0.6.2)
jekyll (>= 3.8.5)
jekyll-include-cache
jekyll-seo-tag (>= 2.0)
rake (>= 12.3.1)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
nokogiri (1.15.4-x86_64-linux)
racc (~> 1.4)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
pdf-reader (2.11.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
hashery (~> 2.0)
ruby-rc4
ttfunk
public_suffix (5.0.3)
racc (1.7.1)
rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.6)
rouge (4.1.3)
ruby-rc4 (0.1.5)
safe_yaml (1.0.5)
sass-embedded (1.69.4)
google-protobuf (~> 3.23)
rake (>= 13.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
timers (4.3.5)
ttfunk (1.7.0)
typhoeus (1.4.0)
ethon (>= 0.9.0)
unicode-display_width (2.5.0)
webrick (1.8.1)
yell (2.2.2)
zeitwerk (2.6.12)
PLATFORMS
x86_64-linux
DEPENDENCIES
html-proofer (= 5.0.8)
jekyll (= 4.3.2)
jekyll-sitemap (= 1.4.0)
just-the-docs (= 0.6.2)
rake (= 13.0.6)
webrick (= 1.8.1)
RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.3.7

View File

@@ -7,18 +7,30 @@ statically generated using [Jekyll][jekyll].
# Local development
To view the website on `localhost`, first follow the [Jekyll installation
instructions][jekyll-docs-installation]. Once done, in this directory execute:
instructions][jekyll-docs-installation]. Once done, run the following Maven
commands in the root of the repository to extract the (test) data from the bug
patterns and Refaster rule collections and to transform this data into a
Jekyll-digestible format. Unless and relevant Java code has been changed, these
commands needs to be executed once.
```sh
mvn -T1C clean install -DskipTests -Dverification.skip
mvn exec:java@generate-docs -pl documentation-support
```
Then to build the website for local development, execute in this directory:
```sh
bundle install
../generate-docs.sh && bundle exec jekyll serve --livereload
bundle exec jekyll serve --livereload
```
The website will now be [available][localhost-port-4000] on port 4000. Source
code modifications (including the result of rerunning `../generate-docs.sh`)
will automatically be reflected. (An exception is `_config.yml`: changes to
this file require a server restart.) Subsequent server restarts do not require
running `bundle install`, unless `Gemfile` has been updated in the interim.
code modifications (including the result of rerunning `mvn
exec:java@generate-docs -pl documentation-support`) will automatically be
reflected. (An exception is `_config.yml`: changes to this file require a
server restart.) Subsequent server restarts do not require running `bundle
install`, unless `Gemfile` has been updated in the interim.
If you are not familiar with Jekyll, be sure to check out its
[documentation][jekyll-docs]. It is recommended to follow the provided

View File

@@ -8,15 +8,40 @@ description: >-
theme: just-the-docs
plugins:
- jekyll-sitemap
- jekyll-sitemap
# Files and directories not to be deployed through GitHub pages.
exclude:
- Gemfile
- Gemfile.lock
- generate-version-compatibility-overview.sh
- README.md
- vendor
- Gemfile
- Gemfile.lock
- generate-version-compatibility-overview.sh
- README.md
- vendor
collections:
bugpatterns:
output: true
refasterrules:
output: true
defaults:
- scope:
type: "bugpatterns"
values:
layout: "bugpattern"
- scope:
type: "refasterrules"
values:
layout: "refasterrule"
just_the_docs:
collections:
bugpatterns:
name: Bug Patterns
nav_fold: true
refasterrules:
name: Refaster Rules
nav_fold: true
# See https://jekyllrb.com/docs/permalinks/#built-in-formats.
permalink: pretty
@@ -25,9 +50,9 @@ permalink: pretty
# See
# https://just-the-docs.github.io/just-the-docs/docs/navigation-structure/#external-navigation-links.
nav_external_links:
- title: Error Prone Support on GitHub
url: https://github.com/PicnicSupermarket/error-prone-support
hide_icon: false
- title: Error Prone Support on GitHub
url: https://github.com/PicnicSupermarket/error-prone-support
hide_icon: false
callouts:
summary:
@@ -40,9 +65,9 @@ callouts:
social:
name: Picnic
links:
- https://github.com/PicnicSupermarket
- https://twitter.com/picnic
- https://www.linkedin.com/company/picnictechnologies
- https://github.com/PicnicSupermarket
- https://twitter.com/picnic
- https://www.linkedin.com/company/picnictechnologies
twitter:
username: picnic
card: summary

View File

@@ -1,6 +1,25 @@
# 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.18.0
compatible:
- "2.30.0"
- "2.31.0"
- "2.32.0"
- version: 0.17.0
compatible:
- "2.29.2"
- "2.29.1"
- "2.29.0"
- "2.28.0"
- "2.27.1"
- "2.27.0"
- "2.26.1"
- "2.26.0"
- "2.25.0"
- "2.24.1"
- "2.24.0"
- "2.23.0"
- version: 0.16.1
compatible:
- "2.29.2"

View File

@@ -0,0 +1,6 @@
ERROR:
color: red
WARNING:
color: yellow
SUGGESTION:
color: green

View File

@@ -0,0 +1,94 @@
---
# XXX: To be implemented:
# - Support for alt names.
# - Support for explanations.
# - Support for "can disable".
# - Support for custom suppression annotations.
layout: default
---
{% capture markdown_layout %}
# {{ page.name }}
{{ page.severity }}
{: .label .label-{{ site.data.severities[page.severity].color }} }
{% for tag in page.tags %}
{{ tag }}
{: .label }
{% endfor %}
<a href="https://github.com/PicnicSupermarket/error-prone-support/blob/master/{{ page.source }}" class="fs-3 btn external" target="_blank">
View source code on GitHub
<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title">
<use xlink:href="#svg-external-link"></use>
</svg>
</a>
{: .summary-title }
> Summary
>
> {{ page.summary }}
{% comment %}
# XXX: Here, include a more elaborate explantion, if available.
{% endcomment %}
{: .note-title }
> Suppression
>
> Suppress false positives by adding the suppression annotation `@SuppressWarnings("{{ page.name }}")` to
> the enclosing element.
>
> Disable this pattern completely by adding `-Xep:{{ page.name }}:OFF` as compiler argument.
> [Learn more][error-prone-flags].
{% comment %}
# XXX: Create an internal page on documenting the usage of compiler flags.
{% endcomment %}
{% if page.replacement or page.identification %}
## Samples
{% comment %}
# XXX: Either make this "Samples" header useful, or drop it. (In which case
# the wrapping conjunctive guard should also go.)
{% endcomment %}
{% if page.replacement %}
### Replacement
Shows the difference in example code before and after the bug pattern is
applied.
{% for diff in page.replacement %}
{% highlight diff %}
{{ diff }}
{% endhighlight %}
{% endfor %}
{% endif %}
{% if page.identification %}
### Identification
Shows code lines which will (not) be flagged by this bug pattern. \
A `//BUG: Diagnostic contains:` comment is placed above any violating line.
{% for source in page.identification %}
{% highlight java %}
{{ source }}
{% endhighlight %}
{% endfor %}
{% endif %}
{% endif %}
[error-prone-flags]: https://errorprone.info/docs/flags
{% endcapture %}
{{ markdown_layout | markdownify }}

View File

@@ -0,0 +1,78 @@
---
layout: default
---
{% capture markdown_layout %}
# {{ page.name }}
{: .no_toc }
{{ page.severity }}
{: .label .label-{{ site.data.severities[page.severity].color }} }
{% for tag in page.tags %}
{{ tag }}
{: .label }
{% endfor %}
<a href="https://github.com/PicnicSupermarket/error-prone-support/blob/master/{{ page.source }}" class="fs-3 btn external" target="_blank">
View source code on GitHub
<svg viewBox="0 0 24 24" aria-labelledby="svg-external-link-title">
<use xlink:href="#svg-external-link"></use>
</svg>
</a>
{: .note-title }
> Suppression
>
> Disable all rules by adding `-XepOpt:Refaster:NamePattern=^(?!{{page.name}}\$).*` as
> compiler argument.
{% comment %}
# XXX: Create an internal page on documenting the usage of compiler flags.
{% endcomment %}
<details open markdown="block">
<summary>
Table of contents
</summary>
{: .text-delta }
1. TOC
{:toc}
</details>
{% for rule in page.rules %}
## {{rule.name}}
{{ page.severity }}
{: .label .label-{{ site.data.severities[rule.severity].color }} }
{% for tag in rule.tags %}
{{ tag }}
{: .label }
{% endfor %}
{: .note-title }
> Suppression
>
> Suppress false positives by adding the suppression annotation `@SuppressWarnings("{{rule.name}}")` to
> the enclosing element.
>
> Disable this rule by adding `-XepOpt:Refaster:NamePattern=^(?!{{page.name}}\${{rule.name}}).*`
> as compiler argument.
{% comment %}
# XXX: Create an internal page on documenting the usage of compiler flags.
{% endcomment %}
### Samples
{: .no_toc .text-delta }
Shows the difference in example code before and after the Refaster rule is
applied.
{% highlight diff %}
{{ rule.diff }}
{% endhighlight %}
{% endfor %}
{% endcapture %}
{{ markdown_layout | markdownify }}

View File

@@ -2,4 +2,4 @@
// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/support/_variables.scss.
// Grid system.
$nav-width: 400px;
$nav-width: 25rem;

View File

@@ -1,8 +0,0 @@
---
layout: default
title: Bug Patterns
nav_order: 2
has_children: true
---
# Bug Patterns

View File

@@ -1,8 +0,0 @@
---
layout: default
title: Refaster Rules
nav_order: 2
has_children: true
---
# Refaster Rules