Compare commits

...

150 Commits

Author SHA1 Message Date
Stephan Schroevers
f84e17b396 Disable external link checking until the website branch is up-to-date with master again 2023-10-21 13:28:26 +02:00
Stephan Schroevers
11022cd958 Fix Refaster exclusion regexes 2023-10-21 13:20:48 +02:00
Stephan Schroevers
f0145f74ab Move SuggestedFixRules test files 2023-10-21 13:20:48 +02:00
Pieter Dirk Soels
86e50aaa7b Improve extractor matching and update README 2023-10-21 13:20:48 +02:00
Pieter Dirk Soels
8bf1bd4105 Move new rule collection 2023-10-21 13:20:48 +02:00
Pieter Dirk Soels
db876b2257 Drop Docgen Maven profile 2023-10-21 13:20:48 +02:00
Pieter Dirk Soels
3427b947cf Fit Refaster extractors into new documentation support setup 2023-10-21 13:20:48 +02:00
Rick Ossendrijver
f3571f13dd 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
2023-10-21 13:20:47 +02:00
Pieter Dirk Soels
c9612f22af PSM-1717 Pass ClassLoader to ServiceLoader 2023-10-21 13:20:47 +02:00
Stephan Schroevers
03a4466878 Fix JDK 11 compatibility 2023-10-21 13:20:47 +02:00
Stephan Schroevers
7146ff1ceb Suggestions 2023-10-21 13:20:47 +02:00
Stephan Schroevers
e116854d3d Suggestions 2023-10-21 13:20:47 +02:00
Stephan Schroevers
379b453e82 Suggestions 2023-10-21 13:20:47 +02:00
Rick Ossendrijver
ecafaa6801 This actually kills the mutant 2023-10-21 13:20:47 +02:00
Rick Ossendrijver
1ef7f5b04b Kill another mutant and drop unused imports 2023-10-21 13:20:47 +02:00
Rick Ossendrijver
3d5a75333b Further simplify testing setup 2023-10-21 13:20:47 +02:00
Rick Ossendrijver
9ee59a6030 Simplify tests 2023-10-21 13:20:47 +02:00
Rick Ossendrijver
4bcba7f2a0 Introduce BugPatternTestExtractor with tests 2023-10-21 13:20:47 +02:00
Picnic-Bot
554a3e634c Upgrade Mockito 5.3.0 -> 5.3.1 (#590)
See:
- https://github.com/mockito/mockito/releases/tag/v5.3.1
- https://github.com/mockito/mockito/compare/v5.3.0...v5.3.1
2023-04-22 14:39:55 +02:00
Picnic-Bot
7a3ae7c646 Upgrade Pitest Git plugins 1.0.7 -> 1.0.8 (#589) 2023-04-21 13:13:47 +02:00
Picnic-Bot
6d24540d01 Upgrade Arcmutate 1.0.2 -> 1.0.3 (#585) 2023-04-21 10:30:30 +02:00
Picnic-Bot
7637ffee24 Upgrade Pitest JUnit 5 Accelerator 1.0.4 -> 1.0.5 (#586) 2023-04-21 10:16:18 +02:00
Picnic-Bot
12d2b52e38 Upgrade Spring Boot 2.7.10 -> 2.7.11 (#588)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.11
- https://github.com/spring-projects/spring-boot/compare/v2.7.10...v2.7.11
2023-04-21 09:17:56 +02:00
Picnic-Bot
53daabe5df Upgrade maven-checkstyle-plugin 3.2.1 -> 3.2.2 (#587)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.2.1%20AND%20fixVersion%20%3C%3D%203.2.2
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.2.1...maven-checkstyle-plugin-3.2.2
2023-04-21 08:21:49 +02:00
Stephan Schroevers
ee0884e65f Introduce AssociativeMethodInvocation check (#560) 2023-04-19 08:55:14 +02:00
Mohamed Sameh
3af81d8b10 Introduce StringIs{,Not}EmptyPredicate Refaster rules (#577) 2023-04-18 09:04:03 +02:00
Mohamed Sameh
ebd64c1077 Introduce AssertThatMapContainsOnlyKeys Refaster rule (#576) 2023-04-18 08:09:17 +02:00
Stephan Schroevers
929f1dd1c7 Introduce OpenSSF Scorecard GitHub action (#574)
And resolve some of the issues it identified.

See https://securityscorecards.dev
2023-04-16 09:56:14 +02:00
Stephan Schroevers
9ddd91a50e Introduce CodeQL security vulnerability analysis (#573)
See https://codeql.github.com
2023-04-15 19:23:25 +02:00
Picnic-Bot
a1227ca710 Upgrade New Relic Java Agent 8.0.1 -> 8.1.0 (#583)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v8.1.0
- https://github.com/newrelic/newrelic-java-agent/compare/v8.0.1...v8.1.0
2023-04-15 16:57:45 +02:00
Picnic-Bot
44e0904357 Upgrade Spring 5.3.26 -> 5.3.27 (#582)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.27
- https://github.com/spring-projects/spring-framework/compare/v5.3.26...v5.3.27
2023-04-15 13:20:18 +02:00
Stephan Schroevers
9b54c73dc0 Have DirectReturn check consider finally blocks (#568) 2023-04-14 12:56:22 +02:00
Stephan Schroevers
8ace5b7e9a Fix and enable SuggestedFixRules tests (#581) 2023-04-13 14:20:58 +02:00
Stephan Schroevers
94ffc5d495 Apply assorted test cleanup (#562)
Summary of changes:
- Inline more `CompilationTestHelper` fields.
- Move inner class to the bottom of the outer class.
- Improve test parameter name.
2023-04-13 12:54:51 +02:00
Stephan Schroevers
977019c5bf Improve contribution documentation (#572)
- Introduce a `./run-full-build.sh` script.
- Explicitly mention that users should run this script before opening a pull
  request.
- Emphasize that many build warnings can be resolved automatically.
- Introduce a `SECURITY.md` file as suggested by GitHub.
2023-04-13 09:10:56 +02:00
Luke Prananta
6514236514 Introduce FluxCollectToImmutableList Refaster rule (#570)
And extend `MonoIdentity` to simplify `mono.map(ImmutableList::copyOf)`
expressions where possible, as the new rule may introduce such cases.
2023-04-13 08:31:45 +02:00
Picnic-Bot
6d23fbdd35 Upgrade Project Reactor 2022.0.5 -> 2022.0.6 (#579)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.6
- https://github.com/reactor/reactor/compare/2022.0.5...2022.0.6
2023-04-12 14:03:31 +02:00
Picnic-Bot
b6f14c073a Upgrade Mockito 5.2.0 -> 5.3.0 (#580)
See:
- https://github.com/mockito/mockito/releases/tag/v5.3.0
- https://github.com/mockito/mockito/compare/v5.2.0...v5.3.0
2023-04-12 08:45:52 +02:00
Luke Prananta
ae22e0ec5e Introduce FluxCollectToImmutableSet Refaster rule (#571) 2023-04-08 00:41:50 +02:00
Stephan Schroevers
6e6f8d9f7b Introduce SourceCode#unwrapMethodInvocation utility method (#561) 2023-04-07 15:05:11 +02:00
Picnic-Bot
68d0bed36c Upgrade Byte Buddy 1.14.3 -> 1.14.4 (#569)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.4
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.3...byte-buddy-1.14.4
2023-04-07 08:43:11 +02:00
Picnic-Bot
2edbc85c28 Upgrade pitest-maven-plugin 1.11.7 -> 1.12.0 (#567)
See:
- https://github.com/hcoles/pitest/releases/tag/1.12.0
- https://github.com/hcoles/pitest/compare/1.11.7...1.12.0
2023-04-06 10:55:01 +02:00
Mohamed Sameh
0fefb6985e Introduce MonoJustOrEmptyOptional Refaster rule (#563)
While there, rename two other rules.
2023-04-05 18:12:33 +02:00
Stephan Schroevers
64f9d6b7a2 Introduce SuggestedFixRules Refaster rule collection (#559) 2023-04-05 11:25:03 +02:00
Picnic-Bot
b0f99e7c0b Upgrade jacoco-maven-plugin 0.8.8 -> 0.8.9 (#565)
See:
- https://github.com/jacoco/jacoco/releases/tag/v0.8.9
- https://github.com/jacoco/jacoco/compare/v0.8.8...v0.8.9
2023-04-05 10:43:22 +02:00
Picnic-Bot
320c4175c9 Upgrade Checker Framework Annotations 3.32.0 -> 3.33.0 (#564)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.33.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.32.0...checker-framework-3.33.0
2023-04-04 08:09:57 +02:00
Picnic-Bot
e9829d93bf Upgrade Forbidden APIs plugin 3.5 -> 3.5.1 (#558)
See:
- https://github.com/policeman-tools/forbidden-apis/wiki/Changes
- https://github.com/policeman-tools/forbidden-apis/compare/3.5...3.5.1
2023-04-01 09:40:16 +02:00
Stephan Schroevers
b273502e88 [maven-release-plugin] prepare for next development iteration 2023-03-31 09:31:01 +02:00
Stephan Schroevers
8c6bd1b6e7 [maven-release-plugin] prepare release v0.9.0 2023-03-31 09:30:59 +02:00
Picnic-Bot
0c1817c589 Upgrade Pitest Git plugins 1.0.6 -> 1.0.7 (#556) 2023-03-31 08:50:04 +02:00
Stephan Schroevers
73cf28e7ff Introduce DirectReturn check (#513) 2023-03-30 20:51:04 +02:00
Picnic-Bot
8a0abf5957 Upgrade Byte Buddy 1.14.2 -> 1.14.3 (#555)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.3
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.2...byte-buddy-1.14.3
2023-03-30 20:39:49 +02:00
Picnic-Bot
5fb4aed3ad Upgrade extra-enforcer-rules 1.6.1 -> 1.6.2 (#554)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/1.6.2
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.6.1...1.6.2
2023-03-30 09:34:58 +02:00
Picnic-Bot
aef9c5da7a Upgrade Forbidden APIs plugin 3.4 -> 3.5 (#553)
See:
- https://github.com/policeman-tools/forbidden-apis/wiki/Changes
- https://github.com/policeman-tools/forbidden-apis/compare/3.4...3.5
2023-03-28 08:09:28 +02:00
Picnic-Bot
7069e7a6d8 Upgrade pitest-maven-plugin 1.11.6 -> 1.11.7 (#552)
See:
- https://github.com/hcoles/pitest/releases/tag/1.11.7
- https://github.com/hcoles/pitest/compare/1.11.6...1.11.7
2023-03-28 07:32:21 +02:00
Bastien Diederichs
334c374ca1 Extend null check Refaster rules (#523)
Summary of changes:
- Replace `CheckNotNull` with `RequireNonNull{,WithMessage}{,Statement}`.
- Extend `Is{,Not}Null`.

Fixes #437.
2023-03-27 22:08:34 +02:00
Mohamed Sameh
57cd084f82 Extend StepVerifierStepIdentity Refaster rule (#541)
By flagging expressions of the form `step.expectNextCount(0)`.
2023-03-27 10:30:25 +02:00
Picnic-Bot
0b3be1b75b Upgrade Spring Boot 2.7.9 -> 2.7.10 (#549)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.10
- https://github.com/spring-projects/spring-boot/compare/v2.7.9...v2.7.10
2023-03-27 10:09:28 +02:00
Picnic-Bot
902538fd4a Upgrade maven-deploy-plugin 3.1.0 -> 3.1.1 (#546)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEPLOY%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.1.1
- https://github.com/apache/maven-deploy-plugin/releases/tag/maven-deploy-plugin-3.1.1
- https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.1.0...maven-deploy-plugin-3.1.1
2023-03-27 09:47:22 +02:00
Picnic-Bot
50f6b770e4 Upgrade maven-install-plugin 3.1.0 -> 3.1.1 (#547)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MINSTALL%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.1.1
- https://github.com/apache/maven-install-plugin/releases/tag/maven-install-plugin-3.1.1
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.1.0...maven-install-plugin-3.1.1
2023-03-27 09:30:46 +02:00
Picnic-Bot
47e0a779bd Upgrade maven-resources-plugin 3.3.0 -> 3.3.1 (#548)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MRESOURCES%20AND%20fixVersion%20%3E%203.3.0%20AND%20fixVersion%20%3C%3D%203.3.1
- https://github.com/apache/maven-resources-plugin/releases/tag/maven-resources-plugin-3.3.1
- https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.3.0...maven-resources-plugin-3.3.1
2023-03-27 09:09:20 +02:00
Picnic-Bot
973d3c3cd9 Upgrade maven-release-plugin 2.5.3 -> 3.0.0 (#540)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MRELEASE%20AND%20fixVersion%20%3E%202.5.3%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M1
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M2
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M3
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M4
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M5
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M6
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0-M7
- https://github.com/apache/maven-release/releases/tag/maven-release-3.0.0
- https://github.com/apache/maven-release/compare/maven-release-2.5.3...maven-release-3.0.0
2023-03-27 08:44:43 +02:00
Picnic-Bot
edb7290e2e Upgrade Checkstyle 10.9.2 -> 10.9.3 (#551)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.9.3
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.9.2...checkstyle-10.9.3
2023-03-27 08:32:43 +02:00
Picnic-Bot
d5c45e003f Upgrade modernizer-maven-plugin 2.5.0 -> 2.6.0 (#550)
See:
- https://github.com/gaul/modernizer-maven-plugin/releases/tag/modernizer-maven-plugin-2.6.0
- https://github.com/gaul/modernizer-maven-plugin/compare/modernizer-maven-plugin-2.5.0...modernizer-maven-plugin-2.6.0
2023-03-26 19:22:16 +02:00
Picnic-Bot
f784c64150 Upgrade pitest-maven-plugin 1.11.5 -> 1.11.6 (#544)
See:
- https://github.com/hcoles/pitest/releases/tag/1.11.6
- https://github.com/hcoles/pitest/compare/1.11.5...1.11.6
2023-03-25 19:57:48 +01:00
Guillaume Toison
978c90db9d Extend set of parameter types recognized by RequestMappingAnnotation (#543)
Additional types recognized:
- `jakarta.servlet.http.HttpServletRequest`
- `jakarta.servlet.http.HttpServletResponse`
- `org.springframework.ui.Model`
- `org.springframework.validation.BindingResult`
2023-03-25 19:48:00 +01:00
Picnic-Bot
ae89a37934 Upgrade swagger-annotations 1.6.9 -> 1.6.10 (#542)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v1.6.10
- https://github.com/swagger-api/swagger-core/compare/v1.6.9...v1.6.10
2023-03-23 09:22:22 +01:00
Bastien Diederichs
8f1d1df747 Introduce BugCheckerRules Refaster rule collection (#526) 2023-03-23 09:01:18 +01:00
Picnic-Bot
04368e9243 Upgrade SLF4J API 2.0.6 -> 2.0.7 (#536)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_2.0.6...v_2.0.7
2023-03-22 18:52:40 +01:00
Picnic-Bot
156df71616 Upgrade Spring 5.3.25 -> 5.3.26 (#539)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.26
- https://github.com/spring-projects/spring-framework/compare/v5.3.25...v5.3.26
2023-03-21 17:28:18 +01:00
Picnic-Bot
64b1c7eea4 Upgrade swagger-annotations 2.2.8 -> 2.2.9 (#538)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.9
- https://github.com/swagger-api/swagger-core/compare/v2.2.8...v2.2.9
2023-03-21 17:15:44 +01:00
Picnic-Bot
80d0d85826 Upgrade Checkstyle 10.9.1 -> 10.9.2 (#537)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.9.2
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.9.1...checkstyle-10.9.2
2023-03-21 11:44:37 +01:00
Stephan Schroevers
d30c99a28f Introduce AssertThatPathContent{,Utf8} Refaster rules (#530) 2023-03-20 13:48:41 +01:00
Picnic-Bot
29c23542da Upgrade pitest-maven-plugin 1.11.4 -> 1.11.5 (#534)
While there, fix `run-mutation-tests.sh` for compatibility with Surefire 3.0.0.

See:
- https://github.com/hcoles/pitest/releases/tag/1.11.5
- https://github.com/hcoles/pitest/compare/1.11.4...1.11.5
2023-03-18 15:10:16 +01:00
Picnic-Bot
62c1c277ae Upgrade Checkstyle 10.8.1 -> 10.9.1 (#535)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.9.0
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.9.1
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.8.1...checkstyle-10.9.1
2023-03-18 14:23:16 +01:00
Picnic-Bot
8580e89008 Upgrade Project Reactor 2022.0.4 -> 2022.0.5 (#533)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.5
- https://github.com/reactor/reactor/compare/2022.0.4...2022.0.5
2023-03-16 15:48:01 +01:00
Stephan Schroevers
06c8b164e9 Upgrade JDKs used by GitHub Actions builds (#521)
Additionally:
- Update the example version numbers mentioned in the issue template.
- Drop some redundant whitespace from `SourceCodeTest` test code.
- Sort some compiler arguments.

See:
- https://www.oracle.com/java/technologies/javase/11-0-17-relnotes.html
- https://www.oracle.com/java/technologies/javase/11-0-18-relnotes.html
- https://www.oracle.com/java/technologies/javase/17-0-5-relnotes.html
- https://www.oracle.com/java/technologies/javase/17-0-6-relnotes.html
- https://www.oracle.com/java/technologies/javase/19-0-1-relnotes.html
- https://www.oracle.com/java/technologies/javase/19-0-2-relnotes.html
2023-03-15 13:26:40 +01:00
Picnic-Bot
fd9d3157bc Upgrade Surefire 2.22.2 -> 3.0.0 (#532)
While there, drop an unnecessary JUnit configuration parameter.

See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20SUREFIRE%20AND%20fixVersion%20%3E%202.22.2%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M1
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M2
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M3
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M4
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M5
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M6
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M7
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M8
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0-M9
- https://github.com/apache/maven-surefire/releases/tag/surefire-3.0.0
- https://github.com/apache/maven-surefire/compare/surefire-2.22.2..surefire-3.0.0
2023-03-15 13:01:20 +01:00
Picnic-Bot
a623f73c1c Upgrade Byte Buddy 1.14.1 -> 1.14.2 (#531)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.2
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.1...byte-buddy-1.14.2
2023-03-15 10:39:02 +01:00
Picnic-Bot
f9d0cd99d6 Upgrade Mockito 5.1.1 -> 5.2.0 (#529)
See:
- https://github.com/mockito/mockito/releases/tag/v5.2.0
- https://github.com/mockito/mockito/compare/v5.1.1...v5.2.0
2023-03-13 15:15:29 +01:00
Picnic-Bot
9bec3de372 Upgrade Pitest Git plugins 1.0.5 -> 1.0.6 (#522) 2023-03-13 10:33:22 +01:00
Picnic-Bot
4164514c5b Upgrade Checkstyle 10.8.0 -> 10.8.1 (#528)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.8.1
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.8.0...checkstyle-10.8.1
2023-03-13 09:58:38 +01:00
Picnic-Bot
c3cd535b16 Upgrade NullAway 0.10.9 -> 0.10.10 (#524)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.9...v0.10.10
2023-03-09 10:04:48 +01:00
Picnic-Bot
64195279cc Upgrade Byte Buddy 1.14.0 -> 1.14.1 (#525)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.1
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.14.0...byte-buddy-1.14.1
2023-03-09 08:36:25 +01:00
Pieter Dirk Soels
61c9f67f66 Introduce MockitoMockClassReference check (#454)
Flags Mockito mock and spy creation expressions that explicitly specify the
type of mock or spy to create, while this information can also be inferred from
context.
2023-03-06 09:54:26 +01:00
Picnic-Bot
4bb14b01ec Upgrade pitest-maven-plugin 1.11.3 -> 1.11.4 (#520)
See:
- https://github.com/hcoles/pitest/releases/tag/1.11.4
- https://github.com/hcoles/pitest/compare/1.11.3...1.11.4
2023-03-04 14:19:29 +01:00
Bastien Diederichs
b267b4dba8 Introduce ImmutableMapCopyOfMapsFilter{Keys,Values} Refaster rules (#517) 2023-03-03 13:09:44 +01:00
Picnic-Bot
03f0e0493b Upgrade Checker Framework Annotations 3.31.0 -> 3.32.0 (#519)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.32.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.31.0...checker-framework-3.32.0
2023-03-03 10:17:22 +01:00
Picnic-Bot
2111c81784 Upgrade pitest-maven-plugin 1.11.1 -> 1.11.3 (#514)
See:
- https://github.com/hcoles/pitest/releases/tag/1.11.2
- https://github.com/hcoles/pitest/releases/tag/1.11.3
- https://github.com/hcoles/pitest/compare/1.11.1...1.11.3
2023-03-03 08:53:01 +01:00
Picnic-Bot
43d50f2ef9 Upgrade Project Reactor 2022.0.3 -> 2022.0.4 (#518)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.4
- https://github.com/reactor/reactor/compare/2022.0.3...2022.0.4
2023-03-03 08:35:36 +01:00
Gijs de Jong
2d972fd975 Introduce JUnitValueSource check (#188)
This new check replaces JUnit `@MethodSource` usages with an equivalent
`@ValueSource` annotation where possible.
2023-03-02 10:45:35 +01:00
Bastien Diederichs
ee265a87ae Introduce FluxCountMapMathToIntExact Refaster rule (#516) 2023-03-02 08:48:33 +01:00
Picnic-Bot
6b4fba62da Upgrade pitest-maven-plugin 1.11.0 -> 1.11.1 (#509)
See:
- https://github.com/hcoles/pitest/releases/tag/1.11.1
- https://github.com/hcoles/pitest/compare/1.11.0...1.11.1
2023-02-27 16:47:31 +01:00
Picnic-Bot
e883e28e34 Upgrade Checkstyle 10.7.0 -> 10.8.0 (#512)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.8.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.7.0...checkstyle-10.8.0
2023-02-27 07:46:00 +01:00
Picnic-Bot
d84de6efba Upgrade Google Java Format 1.15.0 -> 1.16.0 (#511)
See:
- https://github.com/google/google-java-format/releases/tag/v1.16.0
- https://github.com/google/google-java-format/compare/v1.15.0...v1.16.0
2023-02-26 14:40:42 +01:00
Picnic-Bot
4dca61a144 Upgrade New Relic Java Agent 8.0.0 -> 8.0.1 (#508)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v8.0.1
- https://github.com/newrelic/newrelic-java-agent/compare/v8.0.0...v8.0.1
2023-02-25 12:23:02 +01:00
Picnic-Bot
dc9597a603 Upgrade Spring Boot 2.7.8 -> 2.7.9 (#510)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.9
- https://github.com/spring-projects/spring-boot/compare/v2.7.8...v2.7.9
2023-02-24 17:52:45 +01:00
Picnic-Bot
ec9853ac88 Upgrade versions-maven-plugin 2.14.2 -> 2.15.0 (#506)
See:
- https://github.com/mojohaus/versions/releases/tag/2.15.0
- https://github.com/mojohaus/versions-maven-plugin/compare/2.14.2...2.15.0
2023-02-22 08:55:17 +01:00
Giovanni Zotta
5bb1dd1a10 Introduce StreamMapTo{Double,Int,Long}Sum Refaster rules (#497)
As well as a new `IsLambdaExpressionOrMethodReference` matcher.
2023-02-21 16:35:29 +01:00
Picnic-Bot
fd6a45ebd8 Upgrade Project Reactor 2022.0.2 -> 2022.0.3 (#499)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.3
- https://github.com/reactor/reactor/compare/2022.0.2...2022.0.3
2023-02-20 18:58:23 +01:00
Benedek Halasi
82d4677509 Introduce FluxImplicitBlock check (#472) 2023-02-20 10:17:17 +01:00
Picnic-Bot
1fdf1016b7 Upgrade Byte Buddy 1.13.0 -> 1.14.0 (#505)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.14.0
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.13.0...byte-buddy-1.14.0
2023-02-20 08:46:28 +01:00
Picnic-Bot
80e537fce2 Upgrade Pitest Git plugins 1.0.4 -> 1.0.5 (#504) 2023-02-19 12:51:22 +01:00
Picnic-Bot
d85897ea62 Upgrade Checker Framework Annotations 3.30.0 -> 3.31.0 (#502)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.31.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.30.0...checker-framework-3.31.0
2023-02-18 14:38:57 +01:00
Picnic-Bot
c5bde3999d Upgrade maven-javadoc-plugin 3.4.1 -> 3.5.0 (#500)
See:
- https://github.com/apache/maven-javadoc-plugin/releases/tag/maven-javadoc-plugin-3.5.0
- https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.4.1...maven-javadoc-plugin-3.5.0
2023-02-17 13:28:27 +01:00
Rick Ossendrijver
575d494303 Upgrade Maven API 3.8.6 -> 3.8.7 (#498)
See:
- https://maven.apache.org/docs/3.8.7/release-notes.html
- https://github.com/apache/maven/releases/tag/maven-3.8.7
- https://github.com/apache/maven/compare/maven-3.8.6...maven-3.8.7
2023-02-16 13:44:25 +01:00
Picnic-Bot
844ef84d55 Upgrade maven-deploy-plugin 3.0.0 -> 3.1.0 (#495)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEPLOY%20AND%20fixVersion%20%3E%203.0.0%20AND%20fixVersion%20%3C%3D%203.1.0
- https://github.com/apache/maven-deploy-plugin/releases/tag/maven-deploy-plugin-3.1.0
- https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.0.0...maven-deploy-plugin-3.1.0
2023-02-14 11:19:11 +01:00
Eric Staffas
29469cbbfd Introduce ConflictDetection utility class (#478) 2023-02-13 12:43:17 +01:00
Picnic-Bot
da9a6dd270 Upgrade Byte Buddy 1.12.23 -> 1.13.0 (#496)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.13.0
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.23...byte-buddy-1.13.0
2023-02-13 11:35:27 +01:00
Gökhun Çelik
0cb03aa132 Add Gradle installation instructions to README (#430) 2023-02-13 11:18:36 +01:00
Rick Ossendrijver
0f15070883 Introduce documentation-support module (#428)
This new module provides the initial version of a framework for the extraction 
of data from bug checkers and Refaster rules, to be used as input for website
generation.
2023-02-13 09:27:08 +01:00
Stephan Schroevers
14b5fa1feb Update .mvn/maven.config for compatibility with Maven 3.9.0 (#493)
See https://issues.apache.org/jira/browse/MNG-7684
2023-02-08 09:08:09 +01:00
Stephan Schroevers
d1f513373f Enable additional maven-enforcer-plugin rules (#489) 2023-02-06 14:16:05 +01:00
Picnic-Bot
cd1593009b Upgrade Pitest Git plugins 1.0.3 -> 1.0.4 (#490) 2023-02-06 11:49:37 +01:00
Picnic-Bot
0d52414c04 Upgrade Byte Buddy 1.12.22 -> 1.12.23 (#492)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.12.23
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.22...byte-buddy-1.12.23
2023-02-06 08:17:38 +01:00
Picnic-Bot
a55ed9cea9 Upgrade maven-enforcer-plugin 3.1.0 -> 3.2.1 (#487)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MENFORCER%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.2.1%20
- https://github.com/apache/maven-enforcer/releases/tag/enforcer-3.2.1
- https://github.com/apache/maven-enforcer/compare/enforcer-3.1.0...enforcer-3.2.1
2023-02-04 10:33:11 +01:00
Picnic-Bot
9b191f46aa Upgrade Checker Framework Annotations 3.29.0 -> 3.30.0 (#488)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.30.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.29.0...checker-framework-3.30.0
2023-02-03 09:37:00 +01:00
Picnic-Bot
6ea756f3ce Upgrade sortpom-maven-plugin 3.2.0 -> 3.2.1 (#481)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.2.1
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.2.0...sortpom-parent-3.2.1
2023-02-03 09:06:17 +01:00
Picnic-Bot
adbcc4a94f Upgrade Checkstyle 10.6.0 -> 10.7.0 (#486)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.7.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.6.0...checkstyle-10.7.0
2023-02-02 10:19:11 +01:00
Picnic-Bot
0ed2788dbd Upgrade NullAway 0.10.8 -> 0.10.9 (#485)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.8...v0.10.9
2023-02-02 09:22:00 +01:00
Picnic-Bot
04749ffcf5 Upgrade pitest-maven-plugin 1.10.4 -> 1.11.0 (#483)
See https://github.com/hcoles/pitest/compare/1.10.4...1.11.0
2023-01-31 08:49:11 +01:00
Picnic-Bot
37077bd03c Upgrade Mockito 5.1.0 -> 5.1.1 (#482)
See:
- https://github.com/mockito/mockito/releases/tag/v5.1.1
- https://github.com/mockito/mockito/compare/v5.1.0...v5.1.1
2023-01-31 08:30:51 +01:00
Picnic-Bot
4798f7cf5f Upgrade Jackson 2.14.1 -> 2.14.2 (#479)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.14.2
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.14.1...jackson-bom-2.14.2
2023-01-30 09:36:53 +01:00
Picnic-Bot
ac285f0c50 Upgrade Mockito 5.0.0 -> 5.1.0 (#480)
See:
- https://github.com/mockito/mockito/releases/tag/v5.1.0
- https://github.com/mockito/mockito/compare/v5.0.0...v5.1.0
2023-01-30 09:25:08 +01:00
Picnic-Bot
1f3fb08082 Upgrade New Relic Java Agent 7.11.1 -> 8.0.0 (#477)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v8.0.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.11.1...v8.0.0
2023-01-28 10:08:14 +01:00
Stephan Schroevers
9a397aa047 [maven-release-plugin] prepare for next development iteration 2023-01-27 09:20:57 +01:00
Stephan Schroevers
60e74332de [maven-release-plugin] prepare release v0.8.0 2023-01-27 09:20:54 +01:00
Stephan Schroevers
3a94aad3b0 Document MonoFlatMapToFlux Refaster rule limitation (#473) 2023-01-24 15:38:52 +01:00
Picnic-Bot
a5b5f43974 Upgrade TestNG 7.4.0 -> 7.7.1 (#469)
See:
- https://github.com/cbeust/testng/blob/master/CHANGES.txt
- https://github.com/cbeust/testng/releases/tag/7.6.0
- https://github.com/cbeust/testng/releases/tag/7.6.1
- https://github.com/cbeust/testng/releases/tag/7.7.0
- https://github.com/cbeust/testng/releases/tag/7.7.1
- https://github.com/cbeust/testng/compare/7.4.0...7.7.1
2023-01-24 09:45:13 +01:00
Rick Ossendrijver
c212b9a171 Enable Checkstyle's JavadocStyle module (#451)
See: 
- https://checkstyle.org/config_javadoc.html#JavadocStyle
- https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocStyleCheck.html
2023-01-23 11:16:12 +01:00
Picnic-Bot
499f922328 Upgrade Spring 5.3.24 -> 5.3.25 (#460)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.25
- https://github.com/spring-projects/spring-framework/compare/v5.3.24...v5.3.25
2023-01-20 09:59:49 +01:00
Picnic-Bot
5720732b48 Upgrade Spring Boot 2.7.7 -> 2.7.8 (#471)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.8
- https://github.com/spring-projects/spring-boot/compare/v2.7.7...v2.7.8
2023-01-20 08:47:55 +01:00
Phil Werli
81ffd04fe4 Extend MonoIdentity Refaster rule (#470)
By flagging expressions of the form `mono.flux().singleOrEmpty()`.
2023-01-19 13:49:57 +01:00
Picnic-Bot
b2f514f0a5 Upgrade Mockito 4.11.0 -> 5.0.0 (#463)
See:
- https://github.com/mockito/mockito/releases/tag/v5.0.0
- https://github.com/mockito/mockito/compare/v4.11.0...v5.0.0
2023-01-19 10:59:24 +01:00
Picnic-Bot
dad92b5fa6 Upgrade maven-dependency-plugin 3.4.0 -> 3.5.0 (#461)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEP%20AND%20fixVersion%20%3E%203.4.0%20AND%20fixVersion%20%3C%3D%203.5.0%20AND%20statusCategory%20%3D%20Done%20
- https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.4.0...maven-dependency-plugin-3.5.0
2023-01-18 17:13:44 +01:00
Picnic-Bot
6d699f75ad Upgrade maven-checkstyle-plugin 3.2.0 -> 3.2.1 (#459)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.2.0%20AND%20fixVersion%20%3C%3D%203.2.1
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.2.0...maven-checkstyle-plugin-3.2.1
2023-01-18 16:38:54 +01:00
Picnic-Bot
cad0c74dc0 Upgrade nohttp-checkstyle 0.0.10 -> 0.0.11 (#458)
See https://github.com/spring-io/nohttp/compare/0.0.10...0.0.11
2023-01-17 09:15:00 +01:00
Picnic-Bot
a3a1f495f1 Upgrade errorprone-slf4j 0.1.17 -> 0.1.18 (#466)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.18
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.17...v0.1.18
2023-01-17 08:48:31 +01:00
Picnic-Bot
b2a646fc21 Upgrade AssertJ 3.24.1 -> 3.24.2 (#467)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://assertj.github.io/doc/#assertj-guava-release-notes
- https://github.com/assertj/assertj/compare/assertj-build-3.24.1...assertj-build-3.24.2
2023-01-17 08:15:38 +01:00
Picnic-Bot
1194b0c83c Upgrade NullAway 0.10.7 -> 0.10.8 (#464)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.7...v0.10.8
2023-01-17 08:01:48 +01:00
Phil Werli
82a07fde25 Extend MonoIdentity Refaster rule (#465)
By flagging expressions of the form `mono.flux().next()`.
2023-01-17 07:33:56 +01:00
Picnic-Bot
dec3220b5b Upgrade pitest-junit5-plugin 1.1.1 -> 1.1.2 (#462)
See https://github.com/pitest/pitest-junit5-plugin/compare/1.1.1...1.1.2
2023-01-16 08:42:14 +01:00
Stephan Schroevers
79356ac553 Upgrade Error Prone 2.17.0 -> 2.18.0 (#455)
See:
- https://github.com/google/error-prone/releases/tag/v2.18.0
- https://github.com/google/error-prone/compare/v2.17.0...v2.18.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.17.0-picnic-1...v2.18.0-picnic-1
2023-01-11 19:12:39 +01:00
Picnic-Bot
f079c53914 Upgrade Project Reactor 2022.0.1 -> 2022.0.2 (#456)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.2
- https://github.com/reactor/reactor/compare/2022.0.1...2022.0.2
2023-01-11 13:58:41 +01:00
Picnic-Bot
8e24da907d Upgrade JUnit Jupiter 5.9.1 -> 5.9.2 (#457)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.9.2
- https://github.com/junit-team/junit5/compare/r5.9.1...r5.9.2
2023-01-11 11:16:10 +01:00
Picnic-Bot
3c89a1c80d Upgrade AssertJ 3.24.0 -> 3.24.1 (#453)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/assertj/assertj/compare/assertj-build-3.24.0...assertj-build-3.24.1
2023-01-09 09:05:59 +01:00
Rick Ossendrijver
9bd4b16001 Inline most {BugCheckerRefactoring,Compilation}TestHelper fields (#442) 2023-01-08 12:40:20 +01:00
Picnic-Bot
6370452803 Upgrade swagger-annotations 2.2.7 -> 2.2.8 (#452)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.8
- https://github.com/swagger-api/swagger-core/compare/v2.2.7...v2.2.8
2023-01-08 12:16:52 +01:00
Benedek Halasi
feb9abfa91 Introduce MapGetOrDefault Refaster rule (#439)
Fixes #431.
2023-01-06 14:57:12 +01:00
Stephan Schroevers
560f52bad0 [maven-release-plugin] prepare for next development iteration 2023-01-06 11:29:12 +01:00
264 changed files with 6406 additions and 776 deletions

View File

@@ -42,9 +42,9 @@ Please replace this sentence with log output, if applicable.
<!-- Please complete the following information: -->
- Operating system (e.g. MacOS Monterey).
- Java version (i.e. `java --version`, e.g. `17.0.3`).
- Error Prone version (e.g. `2.15.0`).
- Error Prone Support version (e.g. `0.3.0`).
- Java version (i.e. `java --version`, e.g. `17.0.6`).
- Error Prone version (e.g. `2.18.0`).
- Error Prone Support version (e.g. `0.8.0`).
### Additional context

View File

@@ -10,16 +10,16 @@ jobs:
strategy:
matrix:
os: [ ubuntu-22.04 ]
jdk: [ 11.0.16, 17.0.4, 19 ]
jdk: [ 11.0.18, 17.0.6, 19.0.2 ]
distribution: [ temurin ]
experimental: [ false ]
include:
- os: macos-12
jdk: 17.0.4
jdk: 17.0.6
distribution: temurin
experimental: false
- os: windows-2022
jdk: 17.0.4
jdk: 17.0.6
distribution: temurin
experimental: false
- os: ubuntu-22.04
@@ -29,13 +29,15 @@ jobs:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
steps:
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
# on Maven Central, and once against the Picnic Error Prone fork,
# additionally enabling all checks defined in this project and any
# Error Prone checks available only from other artifact repositories.
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
# on Maven Central, and once against the Picnic Error Prone fork,
# additionally enabling all checks defined in this project and any Error
# Prone checks available only from other artifact repositories.
- name: Check out code
uses: actions/checkout@v3.1.0
with:
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:

44
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
# Analyzes the code using GitHub's default CodeQL query database.
# Identified issues are registered with GitHub's code scanning dashboard. When
# a pull request is analyzed, any offending lines are annotated. See
# https://codeql.github.com for details.
name: CodeQL analysis
on:
pull_request:
push:
branches: [ master ]
schedule:
- cron: '0 4 * * 1'
permissions:
contents: read
jobs:
analyze:
strategy:
matrix:
language: [ java, ruby ]
permissions:
contents: read
security-events: write
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.6
distribution: temurin
cache: maven
- name: Initialize CodeQL
uses: github/codeql-action/init@v2.2.11
with:
languages: ${{ matrix.language }}
- name: Perform minimal build
if: matrix.language == 'java'
run: mvn -T1C clean install -DskipTests -Dverification.skip
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v2.2.11
with:
category: /language:${{ matrix.language }}

View File

@@ -3,32 +3,37 @@ on:
pull_request:
push:
branches: [ master, website ]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
permissions:
contents: read
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
persist-credentials: false
- uses: ruby/setup-ruby@v1.126.0
with:
working-directory: ./website
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@v2.1.3
- name: Set up JDK
uses: actions/setup-java@v3.5.1
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Compile project and extract data
# XXX: Remove `-Dverification.skip` and update `website/README.md` once
# module `docgen` has no errors.
run: mvn -T1C clean install -DskipTests -Dverification.skip -Pdocgen
- name: Generate documentation
run: ./generate-docs.sh
- 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
run: bundle exec ruby generate-docs.rb
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.5
with:

36
.github/workflows/openssf-scorecard.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
# Analyzes the code base and GitHub project configuration for adherence to
# security best practices for open source software. Identified issues are
# registered with GitHub's code scanning dashboard. When a pull request is
# analyzed, any offending lines are annotated. See
# https://securityscorecards.dev for details.
name: OpenSSF Scorecard update
on:
pull_request:
push:
branches: [ master ]
schedule:
- cron: '0 4 * * 1'
permissions:
contents: read
jobs:
analyze:
permissions:
contents: read
security-events: write
id-token: write
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
persist-credentials: false
- name: Run OpenSSF Scorecard analysis
uses: ossf/scorecard-action@v2.1.3
with:
results_file: results.sarif
results_format: sarif
publish_results: ${{ github.ref == 'refs/heads/master' }}
- name: Update GitHub's code scanning dashboard
uses: github/codeql-action/upload-sarif@v2.2.11
with:
sarif_file: results.sarif

View File

@@ -15,10 +15,11 @@ jobs:
uses: actions/checkout@v3.1.0
with:
fetch-depth: 2
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
java-version: 17.0.6
distribution: temurin
cache: maven
- name: Run Pitest

View File

@@ -9,20 +9,24 @@ on:
- completed
permissions:
actions: read
checks: write
contents: read
pull-requests: write
jobs:
update-pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
actions: read
checks: write
contents: read
pull-requests: write
runs-on: ubuntu-22.04
steps:
- name: Check out code
uses: actions/checkout@v3.1.0
with:
persist-credentials: false
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
java-version: 17.0.6
distribution: temurin
cache: maven
- name: Download Pitest analysis artifact

View File

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

View File

@@ -48,6 +48,17 @@ be accepted. When in doubt, make sure to first raise an
To the extent possible, the pull request process guards our coding guidelines.
Some pointers:
- Try to make sure that the
[`./run-full-build.sh`][error-prone-support-full-build] script completes
successfully, ideally before opening a pull request. See the [development
instructions][error-prone-support-developing] for details on how to
efficiently resolve many of the errors and warnings that may be reported. (In
particular, make sure to run `mvn fmt:format` and
[`./apply-error-prone-suggestions.sh`][error-prone-support-patch].) That
said, if you feel that the build fails for invalid or debatable reasons, or
if you're unsure how to best resolve an issue, don't let that discourage you
from opening a PR with a failing build; we can have a look at the issue
together!
- Checks should be _topical_: ideally they address a single concern.
- Where possible checks should provide _fixes_, and ideally these are
completely behavior-preserving. In order for a check to be adopted by users
@@ -66,6 +77,9 @@ Some pointers:
sneak in unrelated changes; instead just open more than one pull request 😉.
[error-prone-criteria]: https://errorprone.info/docs/criteria
[error-prone-support-developing]: https://github.com/PicnicSupermarket/error-prone-support/tree/master#-developing-error-prone-support
[error-prone-support-full-build]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-full-build.sh
[error-prone-support-issues]: https://github.com/PicnicSupermarket/error-prone-support/issues
[error-prone-support-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh
[error-prone-support-patch]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/apply-error-prone-suggestions.sh
[error-prone-support-pulls]: https://github.com/PicnicSupermarket/error-prone-support/pulls

View File

@@ -36,7 +36,9 @@ code_][picnic-blog-ep-post].
### Installation
This library is built on top of [Error Prone][error-prone-orig-repo]. To use
it:
it, read the installation guide for Maven or Gradle below.
#### Maven
1. First, follow Error Prone's [installation
guide][error-prone-installation-guide].
@@ -94,6 +96,31 @@ it:
Prone Support. Alternatively reference this project's `self-check` profile
definition. -->
#### Gradle
1. First, follow the [installation guide]
[error-prone-gradle-installation-guide] of the `gradle-errorprone-plugin`.
2. Next, edit your `build.gradle` file to add one or more Error Prone Support
modules:
```groovy
dependencies {
// Error Prone itself.
errorprone("com.google.errorprone:error_prone_core:${errorProneVersion}")
// Error Prone Support's additional bug checkers.
errorprone("tech.picnic.error-prone-support:error-prone-contrib:${errorProneSupportVersion}")
// Error Prone Support's Refaster rules.
errorprone("tech.picnic.error-prone-support:refaster-runner:${errorProneSupportVersion}")
}
tasks.withType(JavaCompile).configureEach {
options.errorprone.disableWarningsInGeneratedCode = true
// Add other Error Prone flags here. See:
// - https://github.com/tbroyer/gradle-errorprone-plugin#configuration
// - https://errorprone.info/docs/flags
}
```
### Seeing it in action
Consider the following example code:
@@ -144,8 +171,15 @@ rules][refaster-rules].
## 👷 Developing Error Prone Support
This is a [Maven][maven] project, so running `mvn clean install` performs a
full clean build and installs the library to your local Maven repository. Some
relevant flags:
full clean build and installs the library to your local Maven repository.
Once you've made changes, the build may fail due to a warning or error emitted
by static code analysis. The flags and commands listed below allow you to
suppress or (in a large subset of cases) automatically fix such cases. Make
sure to carefully check the available options, as this can save you significant
amounts of development time!
Relevant Maven build parameters:
- `-Dverification.warn` makes the warnings and errors emitted by various
plugins and the Java compiler non-fatal, where possible.
@@ -162,19 +196,25 @@ relevant flags:
Pending a release of [google/error-prone#3301][error-prone-pull-3301], this
flag must currently be used in combination with `-Perror-prone-fork`.
Some other commands one may find relevant:
Other highly relevant commands:
- `mvn fmt:format` formats the code using
[`google-java-format`][google-java-format].
- `./run-mutation-tests.sh` runs mutation tests using [Pitest][pitest]. The
results can be reviewed by opening the respective
- [`./run-full-build.sh`][script-run-full-build] builds the project twice,
where the second pass validates compatbility with Picnic's [Error Prone
fork][error-prone-fork-repo] and compliance of the code with any rules
defined within this project. (Consider running this before [opening a pull
request][contributing-pull-request], as the PR checks also perform this
validation.)
- [`./apply-error-prone-suggestions.sh`][script-apply-error-prone-suggestions]
applies Error Prone and Error Prone Support code suggestions to this project.
Before running this command, make sure to have installed the project (`mvn
clean install`) and make sure that the current working directory does not
contain unstaged or uncommited changes.
- [`./run-mutation-tests.sh`][script-run-mutation-tests] runs mutation tests
using [Pitest][pitest]. The results can be reviewed by opening the respective
`target/pit-reports/index.html` files. For more information check the [PIT
Maven plugin][pitest-maven].
- `./apply-error-prone-suggestions.sh` applies Error Prone and Error Prone
Support code suggestions to this project. Before running this command, make
sure to have installed the project (`mvn clean install`) and make sure that
the current working directory does not contain unstaged or uncommited
changes.
When running the project's tests in IntelliJ IDEA, you might see the following
error:
@@ -201,12 +241,19 @@ Want to report or fix a bug, suggest or add a new feature, or improve the
documentation? That's awesome! Please read our [contribution
guidelines][contributing].
### Security
If you want to report a security vulnerablity, please do so through a private
channel; please see our [security policy][security] for details.
[bug-checks]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/
[bug-checks-identity-conversion]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java
[contributing]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md
[contributing-pull-request]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/CONTRIBUTING.md#-opening-a-pull-request
[error-prone-bugchecker]: https://github.com/google/error-prone/blob/master/check_api/src/main/java/com/google/errorprone/bugpatterns/BugChecker.java
[error-prone-fork-jitpack]: https://jitpack.io/#PicnicSupermarket/error-prone
[error-prone-fork-repo]: https://github.com/PicnicSupermarket/error-prone
[error-prone-gradle-installation-guide]: https://github.com/tbroyer/gradle-errorprone-plugin
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
[error-prone-orig-repo]: https://github.com/google/error-prone
[error-prone-pull-3301]: https://github.com/google/error-prone/pull/3301
@@ -219,8 +266,8 @@ guidelines][contributing].
[maven-central-badge]: https://img.shields.io/maven-central/v/tech.picnic.error-prone-support/error-prone-support?color=blue
[maven-central-search]: https://search.maven.org/artifact/tech.picnic.error-prone-support/error-prone-support
[maven]: https://maven.apache.org
[picnic-blog]: https://blog.picnic.nl
[picnic-blog-ep-post]: https://blog.picnic.nl/picnic-loves-error-prone-producing-high-quality-and-consistent-java-code-b8a566be6886
[picnic-blog]: https://blog.picnic.nl
[pitest]: https://pitest.org
[pitest-maven]: https://pitest.org/quickstart/maven
[pr-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg
@@ -229,3 +276,7 @@ guidelines][contributing].
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
[script-apply-error-prone-suggestions]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/apply-error-prone-suggestions.sh
[script-run-full-build]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-full-build.sh
[script-run-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-mutation-tests.sh
[security]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/SECURITY.md

23
SECURITY.md Normal file
View File

@@ -0,0 +1,23 @@
# Security policy
We take security seriously. We are mindful of Error Prone Support's place in
the software supply chain, and the risks and responsibilities that come with
this.
## Supported versions
This project uses [semantic versioning][semantic-versioning]. In general, only
the latest version of this software is supported. That said, if users have a
compelling reason to ask for patch release of an older major release, then we
will seriously consider such a request. We do urge users to stay up-to-date and
use the latest release where feasible.
## Reporting a vulnerability
To report a vulnerability, please visit the [security
advisories][security-advisories] page and click _Report a vulnerability_. We
will take such reports seriously and work with you to resolve the issue in a
timely manner.
[security-advisories]: https://github.com/PicnicSupermarket/error-prone-support/security/advisories
[semantic-versioning]: https://semver.org

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.9.1-SNAPSHOT</version>
</parent>
<artifactId>documentation-support</artifactId>
<name>Picnic :: Error Prone Support :: Documentation Support</name>
<description>Data extraction support for the purpose of documentation generation.</description>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</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-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,114 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import java.util.Optional;
import javax.lang.model.element.AnnotationValue;
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
/**
* An {@link Extractor} that describes how to extract data from a {@code @BugPattern} annotation.
*/
@Immutable
@AutoService(Extractor.class)
public final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
/** Instantiates a new {@link BugPatternExtractor} instance. */
public BugPatternExtractor() {}
@Override
public String identifier() {
return "bugpattern";
}
@Override
public Optional<BugPatternDocumentation> tryExtract(ClassTree tree, VisitorState state) {
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
if (annotation == null) {
return Optional.empty();
}
return Optional.of(
new AutoValue_BugPatternExtractor_BugPatternDocumentation(
symbol.getQualifiedName().toString(),
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
ImmutableList.copyOf(annotation.altNames()),
annotation.link(),
ImmutableList.copyOf(annotation.tags()),
annotation.summary(),
annotation.explanation(),
annotation.severity(),
annotation.disableable(),
annotation.documentSuppression()
? getSuppressionAnnotations(tree)
: ImmutableList.of()));
}
/**
* Returns the fully-qualified class names of suppression annotations specified by the {@link
* BugPattern} annotation located on the given tree.
*
* @implNote This method cannot simply invoke {@link BugPattern#suppressionAnnotations()}, as that
* will yield an "Attempt to access Class objects for TypeMirrors" exception.
*/
private static ImmutableList<String> getSuppressionAnnotations(ClassTree tree) {
AnnotationTree annotationTree =
ASTHelpers.getAnnotationWithSimpleName(
ASTHelpers.getAnnotations(tree), BugPattern.class.getSimpleName());
requireNonNull(annotationTree, "BugPattern annotation must be present");
Attribute.Array types =
doCast(
AnnotationMirrors.getAnnotationValue(
ASTHelpers.getAnnotationMirror(annotationTree), "suppressionAnnotations"),
Attribute.Array.class);
return types.getValue().stream()
.map(v -> doCast(v, Attribute.Class.class).classType.toString())
.collect(toImmutableList());
}
@SuppressWarnings("unchecked")
private static <T extends AnnotationValue> T doCast(AnnotationValue value, Class<T> target) {
verify(target.isInstance(value), "Value '%s' is not of type '%s'", value, target);
return (T) value;
}
@AutoValue
abstract static class BugPatternDocumentation {
abstract String fullyQualifiedName();
abstract String name();
abstract ImmutableList<String> altNames();
abstract String link();
abstract ImmutableList<String> tags();
abstract String summary();
abstract String explanation();
abstract SeverityLevel severityLevel();
abstract boolean canDisable();
abstract ImmutableList<String> suppressionAnnotations();
}
}

View File

@@ -0,0 +1,200 @@
package tech.picnic.errorprone.documentation;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.classLiteral;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreeScanner;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestDocumentation;
/**
* An {@link Extractor} that describes how to extract data from classes that test a {@code
* BugChecker}.
*/
@Immutable
@AutoService(Extractor.class)
public final class BugPatternTestExtractor implements Extractor<BugPatternTestDocumentation> {
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
private static final Matcher<Tree> JUNIT_TEST_METHOD =
toType(MethodTree.class, hasAnnotation("org.junit.jupiter.api.Test"));
private static final Matcher<MethodInvocationTree> BUG_PATTERN_TEST_METHOD =
allOf(
staticMethod()
.onDescendantOfAny(
"com.google.errorprone.CompilationTestHelper",
"com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("newInstance"),
argument(0, classLiteral(anything())));
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
public BugPatternTestExtractor() {}
@Override
public String identifier() {
return "bugpattern-test";
}
// XXX: Improve support for correctly extracting multiple sources from a single
// `{BugCheckerRefactoring,Compilation}TestHelper` test.
@Override
public Optional<BugPatternTestDocumentation> tryExtract(ClassTree tree, VisitorState state) {
return getClassUnderTest(tree)
.filter(bugPatternName -> testsBugPattern(bugPatternName, tree, state))
.map(
bugPatternName -> {
CollectBugPatternTests scanner = new CollectBugPatternTests();
for (Tree m : tree.getMembers()) {
if (JUNIT_TEST_METHOD.matches(m, state)) {
scanner.scan(m, state);
}
}
return new AutoValue_BugPatternTestExtractor_BugPatternTestDocumentation(
bugPatternName, scanner.getIdentificationTests(), scanner.getReplacementTests());
});
}
private static boolean testsBugPattern(
String bugPatternName, ClassTree tree, VisitorState state) {
AtomicBoolean result = new AtomicBoolean(false);
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, @Nullable Void v) {
if (BUG_PATTERN_TEST_METHOD.matches(node, state)) {
MemberSelectTree firstArgumentTree = (MemberSelectTree) node.getArguments().get(0);
result.compareAndSet(
/* expectedValue= */ false,
bugPatternName.equals(firstArgumentTree.getExpression().toString()));
}
return super.visitMethodInvocation(node, v);
}
}.scan(tree, null);
return result.get();
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
private static final class CollectBugPatternTests
extends TreeScanner<@Nullable Void, VisitorState> {
private static final Matcher<ExpressionTree> IDENTIFICATION_SOURCE_LINES =
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines");
private static final Matcher<ExpressionTree> REPLACEMENT_INPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines");
private static final Matcher<ExpressionTree> REPLACEMENT_OUTPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
private final List<String> identificationTests = new ArrayList<>();
private final List<BugPatternReplacementTestDocumentation> replacementTests = new ArrayList<>();
public ImmutableList<String> getIdentificationTests() {
return ImmutableList.copyOf(identificationTests);
}
public ImmutableList<BugPatternReplacementTestDocumentation> getReplacementTests() {
return ImmutableList.copyOf(replacementTests);
}
// XXX: Consider:
// - Whether to omit or handle differently identification tests without `// BUG: Diagnostic
// (contains|matches)` markers.
// - Whether to omit or handle differently replacement tests with identical input and output.
// (Though arguably we should have a separate checker which replaces such cases with
// `.expectUnchanged()`.)
// - Whether to track `.expectUnchanged()` test cases.
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) {
if (IDENTIFICATION_SOURCE_LINES.matches(node, state)) {
getSourceCode(node).ifPresent(identificationTests::add);
} else if (REPLACEMENT_OUTPUT.matches(node, state)) {
ExpressionTree receiver = ASTHelpers.getReceiver(node);
// XXX: Make this code nicer.
if (REPLACEMENT_INPUT.matches(receiver, state)) {
getSourceCode(node)
.ifPresent(
output ->
getSourceCode((MethodInvocationTree) receiver)
.ifPresent(
input ->
replacementTests.add(
new AutoValue_BugPatternTestExtractor_BugPatternReplacementTestDocumentation(
input, output))));
}
}
return super.visitMethodInvocation(node, state);
}
// XXX: Duplicated from `ErrorProneTestSourceFormat`. Can we do better?
private static Optional<String> getSourceCode(MethodInvocationTree tree) {
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
String value = ASTHelpers.constValue(sourceLine, String.class);
if (value == null) {
return Optional.empty();
}
source.append(value).append('\n');
}
return Optional.of(source.toString());
}
}
// XXX: Rename?
@AutoValue
abstract static class BugPatternTestDocumentation {
abstract String name();
abstract ImmutableList<String> identificationTests();
abstract ImmutableList<BugPatternReplacementTestDocumentation> replacementTests();
}
// XXX: Rename?
@AutoValue
abstract static class BugPatternReplacementTestDocumentation {
abstract String inputLines();
abstract String outputLines();
}
}

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.tools.javac.api.BasicJavacTask;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A compiler {@link Plugin} that analyzes and extracts relevant information for documentation
* purposes from processed files.
*/
// XXX: Find a better name for this class; it doesn't generate documentation per se.
@AutoService(Plugin.class)
public final class DocumentationGenerator implements Plugin {
@VisibleForTesting static final String OUTPUT_DIRECTORY_FLAG = "-XoutputDirectory";
private static final Pattern OUTPUT_DIRECTORY_FLAG_PATTERN =
Pattern.compile(Pattern.quote(OUTPUT_DIRECTORY_FLAG) + "=(.*)");
/** Instantiates a new {@link DocumentationGenerator} instance. */
public DocumentationGenerator() {}
@Override
public String getName() {
return getClass().getSimpleName();
}
@Override
public void init(JavacTask javacTask, String... args) {
checkArgument(args.length == 1, "Precisely one path must be provided");
javacTask.addTaskListener(
new DocumentationGeneratorTaskListener(
((BasicJavacTask) javacTask).getContext(), getOutputPath(args[0])));
}
@VisibleForTesting
static Path getOutputPath(String pathArg) {
Matcher matcher = OUTPUT_DIRECTORY_FLAG_PATTERN.matcher(pathArg);
checkArgument(
matcher.matches(), "'%s' must be of the form '%s=<value>'", pathArg, OUTPUT_DIRECTORY_FLAG);
String path = matcher.group(1);
try {
return Path.of(path);
} catch (InvalidPathException e) {
throw new IllegalArgumentException(String.format("Invalid path '%s'", path), e);
}
}
}

View File

@@ -0,0 +1,109 @@
package tech.picnic.errorprone.documentation;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.util.Context;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
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;
/**
* A {@link TaskListener} that identifies and extracts relevant content for documentation generation
* and writes it to disk.
*/
// XXX: Find a better name for this class; it doesn't generate documentation per se.
final class DocumentationGeneratorTaskListener implements TaskListener {
@SuppressWarnings({"rawtypes", "unchecked"})
private static final ImmutableList<Extractor<?>> EXTRACTORS =
(ImmutableList)
ImmutableList.copyOf(
ServiceLoader.load(
Extractor.class, DocumentationGeneratorTaskListener.class.getClassLoader()));
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
private final Context context;
private final Path docsPath;
DocumentationGeneratorTaskListener(Context context, Path path) {
this.context = context;
this.docsPath = path;
}
@Override
public void started(TaskEvent taskEvent) {
if (taskEvent.getKind() == Kind.ANALYZE) {
createDocsDirectory();
}
}
@Override
public void finished(TaskEvent taskEvent) {
if (taskEvent.getKind() != Kind.ANALYZE) {
return;
}
JavaFileObject sourceFile = taskEvent.getSourceFile();
CompilationUnitTree compilationUnit = taskEvent.getCompilationUnit();
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
if (sourceFile == null || compilationUnit == null || classTree == null) {
return;
}
VisitorState state =
VisitorState.createForUtilityPurposes(context)
.withPath(new TreePath(new TreePath(compilationUnit), classTree));
for (Extractor<?> extractor : EXTRACTORS) {
extractor
.tryExtract(classTree, state)
.ifPresent(
data ->
writeToFile(
extractor.identifier(), getSimpleClassName(sourceFile.toUri()), data));
}
}
private void createDocsDirectory() {
try {
Files.createDirectories(docsPath);
} catch (IOException e) {
throw new IllegalStateException(
String.format("Error while creating directory with path '%s'", docsPath), e);
}
}
private <T> void writeToFile(String identifier, String className, T data) {
File file = docsPath.resolve(String.format("%s-%s.json", identifier, className)).toFile();
try (FileWriter fileWriter = new FileWriter(file, UTF_8)) {
OBJECT_MAPPER.writeValue(fileWriter, data);
} catch (IOException e) {
throw new UncheckedIOException(String.format("Cannot write to file '%s'", file.getPath()), e);
}
}
private static String getSimpleClassName(URI path) {
return Paths.get(path).getFileName().toString().replace(".java", "");
}
}

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.documentation;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import java.util.Optional;
/**
* Interface implemented by classes that define how to extract data of some type {@link T} from a
* given {@link ClassTree}.
*
* @param <T> The type of data that is extracted.
*/
@Immutable
interface Extractor<T> {
/**
* Returns the unique identifier of this extractor.
*
* @return A non-{@code null} string.
*/
String identifier();
/**
* Attempts to extract an instance of type {@link T} using the provided arguments.
*
* @param tree The {@link ClassTree} to analyze and from which to extract an instance of type
* {@link T}.
* @param state A {@link VisitorState} describing the context in which the given {@link ClassTree}
* is found.
* @return An instance of type {@link T}, if possible.
*/
Optional<T> tryExtract(ClassTree tree, VisitorState state);
}

View File

@@ -0,0 +1,65 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import java.util.Optional;
import java.util.regex.Pattern;
import tech.picnic.errorprone.documentation.models.RefasterTemplateCollectionTestData;
import tech.picnic.errorprone.documentation.models.RefasterTemplateTestData;
@Immutable
@AutoService(Extractor.class)
public final class RefasterTestInputExtractor
implements Extractor<RefasterTemplateCollectionTestData> {
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
@Override
public String identifier() {
return "refaster-test-input";
}
@Override
public Optional<RefasterTemplateCollectionTestData> tryExtract(
ClassTree tree, VisitorState state) {
if (!state.getPath().getCompilationUnit().getPackageName().toString().contains("input")) {
return Optional.empty();
}
Optional<String> className = getClassUnderTest(tree);
if (className.isEmpty()) {
return Optional.empty();
}
ImmutableList<RefasterTemplateTestData> templateTests =
tree.getMembers().stream()
.filter(m -> m instanceof MethodTree)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m ->
RefasterTemplateTestData.create(
m.getName().toString().replace("test", ""), getSourceCode(m, state)))
.collect(toImmutableList());
return Optional.of(
RefasterTemplateCollectionTestData.create(className.orElseThrow(), true, templateTests));
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
// XXX: Duplicated from `SourceCode`. Can we do better?
private String getSourceCode(MethodTree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
}

View File

@@ -0,0 +1,65 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import java.util.Optional;
import java.util.regex.Pattern;
import tech.picnic.errorprone.documentation.models.RefasterTemplateCollectionTestData;
import tech.picnic.errorprone.documentation.models.RefasterTemplateTestData;
@Immutable
@AutoService(Extractor.class)
public final class RefasterTestOutputExtractor
implements Extractor<RefasterTemplateCollectionTestData> {
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
@Override
public String identifier() {
return "refaster-test-output";
}
@Override
public Optional<RefasterTemplateCollectionTestData> tryExtract(
ClassTree tree, VisitorState state) {
if (!state.getPath().getCompilationUnit().getPackageName().toString().contains("output")) {
return Optional.empty();
}
Optional<String> className = getClassUnderTest(tree);
if (className.isEmpty()) {
return Optional.empty();
}
ImmutableList<RefasterTemplateTestData> templateTests =
tree.getMembers().stream()
.filter(m -> m instanceof MethodTree)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m ->
RefasterTemplateTestData.create(
m.getName().toString().replace("test", ""), getSourceCode(m, state)))
.collect(toImmutableList());
return Optional.of(
RefasterTemplateCollectionTestData.create(className.orElseThrow(), true, templateTests));
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
// XXX: Duplicated from `SourceCode`. Can we do better?
private String getSourceCode(MethodTree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateCollectionData;
/**
* Object containing all data related to a Refaster template collection. This is solely used for
* serialization.
*/
@AutoValue
public abstract class RefasterTemplateCollectionData {
public static RefasterTemplateCollectionData create(
String name, String description, String link, List<RefasterTemplateData> templates) {
return new AutoValue_RefasterTemplateCollectionData(name, description, link, templates);
}
abstract String name();
abstract String description();
abstract String link();
abstract List<RefasterTemplateData> templates();
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateCollectionTestData;
@AutoValue
public abstract class RefasterTemplateCollectionTestData {
public static RefasterTemplateCollectionTestData create(
String templateCollection, boolean isInput, List<RefasterTemplateTestData> templatesTests) {
return new AutoValue_RefasterTemplateCollectionTestData(
templateCollection, isInput, templatesTests);
}
abstract String templateCollection();
abstract boolean isInput();
abstract List<RefasterTemplateTestData> templateTests();
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import com.google.errorprone.BugPattern.SeverityLevel;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateData;
@AutoValue
public abstract class RefasterTemplateData {
public static RefasterTemplateData create(
String name, String description, String link, SeverityLevel severityLevel) {
return new AutoValue_RefasterTemplateData(name, description, link, severityLevel);
}
abstract String name();
abstract String description();
abstract String link();
abstract SeverityLevel severityLevel();
}

View File

@@ -0,0 +1,15 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateTestData;
@AutoValue
public abstract class RefasterTemplateTestData {
public static RefasterTemplateTestData create(String templateName, String templateTestContent) {
return new AutoValue_RefasterTemplateTestData(templateName, templateTestContent);
}
abstract String templateName();
abstract String templateTestContent();
}

View File

@@ -0,0 +1,7 @@
/**
* A Java compiler plugin that extracts data from compiled classes, in support of the Error Prone
* Support documentation.
*/
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.documentation;

View File

@@ -0,0 +1,148 @@
package tech.picnic.errorprone.documentation;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.io.Resources;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.ClassTree;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
final class BugPatternExtractorTest {
@Test
void noBugPattern(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerWithoutAnnotation.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"public final class TestCheckerWithoutAnnotation extends BugChecker {}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void minimalBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"MinimalBugChecker.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"@BugPattern(summary = \"MinimalBugChecker summary\", severity = SeverityLevel.ERROR)",
"public final class MinimalBugChecker extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-MinimalBugChecker.json",
"bugpattern-documentation-minimal.json");
}
@Test
void completeBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"CompleteBugChecker.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"@BugPattern(",
" name = \"OtherName\",",
" summary = \"CompleteBugChecker summary\",",
" linkType = BugPattern.LinkType.CUSTOM,",
" link = \"https://error-prone.picnic.tech\",",
" explanation = \"Example explanation\",",
" severity = SeverityLevel.SUGGESTION,",
" altNames = \"Check\",",
" tags = BugPattern.StandardTags.SIMPLIFICATION,",
" disableable = false,",
" suppressionAnnotations = {BugPattern.class, Test.class})",
"public final class CompleteBugChecker extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-CompleteBugChecker.json",
"bugpattern-documentation-complete.json");
}
@Test
void undocumentedSuppressionBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"UndocumentedSuppressionBugPattern.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"@BugPattern(",
" summary = \"UndocumentedSuppressionBugPattern summary\",",
" severity = SeverityLevel.WARNING,",
" documentSuppression = false)",
"public final class UndocumentedSuppressionBugPattern extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-UndocumentedSuppressionBugPattern.json",
"bugpattern-documentation-undocumented-suppression.json");
}
@Test
void bugPatternAnnotationIsAbsent() {
CompilationTestHelper.newInstance(TestChecker.class, getClass())
.addSourceLines(
"TestChecker.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"// BUG: Diagnostic contains: Can extract: false",
"public final class TestChecker extends BugChecker {}")
.doTest();
}
private static void verifyFileMatchesResource(
Path outputDirectory, String fileName, String resourceName) throws IOException {
assertThat(outputDirectory.resolve(fileName))
.content(UTF_8)
.isEqualToIgnoringWhitespace(getResource(resourceName));
}
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
// text blocks in this class. (This also requires renaming the `verifyFileMatchesResource`
// method.)
private static String getResource(String resourceName) throws IOException {
return Resources.toString(
Resources.getResource(BugPatternExtractorTest.class, resourceName), UTF_8);
}
/** A {@link BugChecker} that validates the {@link BugPatternExtractor}. */
@BugPattern(summary = "Validates `BugPatternExtractor` extraction", severity = ERROR)
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
String.format(
"Can extract: %s", new BugPatternExtractor().tryExtract(tree, state).isPresent()))
.build();
}
}
}

View File

@@ -0,0 +1,352 @@
package tech.picnic.errorprone.documentation;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.io.Resources;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
final class BugPatternTestExtractorTest {
@Test
void noBugPatternTest(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerWithoutAnnotation.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"public final class TestCheckerWithoutAnnotation extends BugChecker {}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void bugPatternTestFileWithoutTestSuffix(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerWithWrongSuffix.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerWithoutTestSuffix {",
" private static class TestCheckerWithout extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(TestCheckerWithout.class, getClass());",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void minimalBugPatternTest(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(TestChecker.class, getClass());",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-minimal.json");
}
@Test
void differentBugPatternAsClassVariableTest(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerTest {",
" private static class DifferentChecker extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(DifferentChecker.class, getClass());",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void differentBugPatternAsLocalVariable(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class DifferentChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(DifferentChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void bugPatternTestSingleIdentification(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification.json");
}
@Test
void bugPatternTestIdentificationMultipleSourceLines(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"package pkg;",
"",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .addSourceLines(\"B.java\", \"class B {}\")",
" .doTest();",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification-two-sources.json");
}
@Test
void bugPatternTestSingleReplacement(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement.json");
}
@Test
void bugPatternTestMultipleReplacementSources(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .addInputLines(\"B.java\", \"class B {}\")",
" .addOutputLines(\"B.java\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement-two-sources.json");
}
@Test
void bugPatternReplacementExpectUnchanged(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .expectUnchanged()",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement-expect-unchanged.json");
}
@Test
void bugPatternTestIdentificationAndReplacement(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification-and-replacement.json");
}
@Test
void bugPatternTestMultipleIdentificationAndReplacement(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"package pkg;",
"",
"import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;",
"",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"",
" @Test",
" void identification2() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"B.java\", \"class B {}\")",
" .doTest();",
" }",
"",
" @Test",
" void replacementFirstSuggestedFix() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"",
" @Test",
" void replacementSecondSuggestedFix() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .setFixChooser(SECOND)",
" .addInputLines(\"B.java\", \"class B {}\")",
" .addOutputLines(\"B.java\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-multiple-identification-and-replacement.json");
}
private static void verifyFileMatchesResource(
Path outputDirectory, String fileName, String resourceName) throws IOException {
assertThat(outputDirectory.resolve(fileName))
.content(UTF_8)
.isEqualToIgnoringWhitespace(getResource(resourceName));
}
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
// text blocks in this class. (This also requires renaming the `verifyFileMatchesResource`
// method.)
private static String getResource(String resourceName) throws IOException {
return Resources.toString(
Resources.getResource(BugPatternTestExtractorTest.class, resourceName), UTF_8);
}
}

View File

@@ -0,0 +1,45 @@
package tech.picnic.errorprone.documentation;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.FileManagers;
import com.google.errorprone.FileObjects;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.file.JavacFileManager;
import java.nio.file.Path;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
// XXX: Generalize and move this class so that it can also be used by `refaster-compiler`.
// XXX: Add support for this class to the `ErrorProneTestHelperSourceFormat` check.
public final class Compilation {
private Compilation() {}
public static void compileWithDocumentationGenerator(
Path outputDirectory, String fileName, String... lines) {
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), fileName, lines);
}
public static void compileWithDocumentationGenerator(
String outputDirectory, String fileName, String... lines) {
compile(
ImmutableList.of("-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory),
FileObjects.forSourceLines(fileName, lines));
}
private static void compile(ImmutableList<String> options, JavaFileObject javaFileObject) {
JavacFileManager javacFileManager = FileManagers.testFileManager();
JavaCompiler compiler = JavacTool.create();
JavacTaskImpl task =
(JavacTaskImpl)
compiler.getTask(
null,
javacFileManager,
null,
options,
ImmutableList.of(),
ImmutableList.of(javaFileObject));
task.call();
}
}

View File

@@ -0,0 +1,138 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.attribute.AclEntryPermission.ADD_SUBDIRECTORY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclFileAttributeView;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.io.TempDir;
final class DocumentationGeneratorTaskListenerTest {
@EnabledOnOs(WINDOWS)
@Test
void readOnlyFileSystemWindows(@TempDir Path outputDirectory) throws IOException {
AclFileAttributeView view =
Files.getFileAttributeView(outputDirectory, AclFileAttributeView.class);
view.setAcl(
view.getAcl().stream()
.map(
entry ->
AclEntry.newBuilder(entry)
.setPermissions(
Sets.difference(entry.permissions(), ImmutableSet.of(ADD_SUBDIRECTORY)))
.build())
.collect(toImmutableList()));
readOnlyFileSystemFailsToWrite(outputDirectory.resolve("nonexistent"));
}
@DisabledOnOs(WINDOWS)
@Test
void readOnlyFileSystemNonWindows(@TempDir Path outputDirectory) {
assertThat(outputDirectory.toFile().setWritable(false))
.describedAs("Failed to make test directory unwritable")
.isTrue();
readOnlyFileSystemFailsToWrite(outputDirectory.resolve("nonexistent"));
}
private static void readOnlyFileSystemFailsToWrite(Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory, "A.java", "class A {}"))
.hasRootCauseInstanceOf(FileSystemException.class)
.hasCauseInstanceOf(IllegalStateException.class)
.hasMessageEndingWith("Error while creating directory with path '%s'", outputDirectory);
}
@Test
void noClassNoOutput(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(outputDirectory, "A.java", "package pkg;");
assertThat(outputDirectory).isEmptyDirectory();
}
@Test
void excessArguments(@TempDir Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory.toAbsolutePath() + " extra-arg", "A.java", "package pkg;"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Precisely one path must be provided");
}
@Test
void extraction(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"DocumentationGeneratorTaskListenerTestClass.java",
"class DocumentationGeneratorTaskListenerTestClass {}");
// XXX: Once we support only JDK 15+, use a text block for the `expected` string.
assertThat(
outputDirectory.resolve(
"documentation-generator-task-listener-test-DocumentationGeneratorTaskListenerTestClass.json"))
.content(UTF_8)
.isEqualToIgnoringWhitespace(
"{\"className\":\"DocumentationGeneratorTaskListenerTestClass\",\"path\":[\"CLASS: DocumentationGeneratorTaskListenerTestClass\",\"COMPILATION_UNIT\"]}");
}
@Immutable
@AutoService(Extractor.class)
public static final class TestExtractor implements Extractor<ExtractionParameters> {
@Override
public String identifier() {
return "documentation-generator-task-listener-test";
}
@Override
public Optional<ExtractionParameters> tryExtract(ClassTree tree, VisitorState state) {
return Optional.of(tree.getSimpleName().toString())
.filter(n -> n.contains(DocumentationGeneratorTaskListenerTest.class.getSimpleName()))
.map(
className ->
new AutoValue_DocumentationGeneratorTaskListenerTest_ExtractionParameters(
className,
Streams.stream(state.getPath())
.map(TestExtractor::describeTree)
.collect(toImmutableList())));
}
private static String describeTree(Tree tree) {
return (tree instanceof ClassTree)
? String.join(": ", String.valueOf(tree.getKind()), ((ClassTree) tree).getSimpleName())
: tree.getKind().toString();
}
}
@AutoValue
abstract static class ExtractionParameters {
abstract String className();
abstract ImmutableList<String> path();
}
}

View File

@@ -0,0 +1,38 @@
package tech.picnic.errorprone.documentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static tech.picnic.errorprone.documentation.DocumentationGenerator.OUTPUT_DIRECTORY_FLAG;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
final class DocumentationGeneratorTest {
@ParameterizedTest
@ValueSource(strings = {"bar", "foo"})
void getOutputPath(String path) {
assertThat(DocumentationGenerator.getOutputPath(OUTPUT_DIRECTORY_FLAG + '=' + path))
.isEqualTo(Path.of(path));
}
@ParameterizedTest
@ValueSource(strings = {"", "-XoutputDirectory", "invalidOption=Test", "nothing"})
void getOutputPathWithInvalidArgument(String pathArg) {
assertThatThrownBy(() -> DocumentationGenerator.getOutputPath(pathArg))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("'%s' must be of the form '%s=<value>'", pathArg, OUTPUT_DIRECTORY_FLAG);
}
@Test
void getOutputPathWithInvalidPath() {
String basePath = "path-with-null-char-\0";
assertThatThrownBy(
() -> DocumentationGenerator.getOutputPath(OUTPUT_DIRECTORY_FLAG + '=' + basePath))
.isInstanceOf(IllegalArgumentException.class)
.hasCauseInstanceOf(InvalidPathException.class)
.hasMessageEndingWith("Invalid path '%s'", basePath);
}
}

View File

@@ -0,0 +1,19 @@
{
"fullyQualifiedName": "pkg.CompleteBugChecker",
"name": "OtherName",
"altNames": [
"Check"
],
"link": "https://error-prone.picnic.tech",
"tags": [
"Simplification"
],
"summary": "CompleteBugChecker summary",
"explanation": "Example explanation",
"severityLevel": "SUGGESTION",
"canDisable": false,
"suppressionAnnotations": [
"com.google.errorprone.BugPattern",
"org.junit.jupiter.api.Test"
]
}

View File

@@ -0,0 +1,14 @@
{
"fullyQualifiedName": "pkg.MinimalBugChecker",
"name": "MinimalBugChecker",
"altNames": [],
"link": "",
"tags": [],
"summary": "MinimalBugChecker summary",
"explanation": "",
"severityLevel": "ERROR",
"canDisable": true,
"suppressionAnnotations": [
"java.lang.SuppressWarnings"
]
}

View File

@@ -0,0 +1,12 @@
{
"fullyQualifiedName": "pkg.UndocumentedSuppressionBugPattern",
"name": "UndocumentedSuppressionBugPattern",
"altNames": [],
"link": "",
"tags": [],
"summary": "UndocumentedSuppressionBugPattern summary",
"explanation": "",
"severityLevel": "WARNING",
"canDisable": true,
"suppressionAnnotations": []
}

View File

@@ -0,0 +1,12 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n"
],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"name": "TestChecker",
"identificationTests": [
"class B {}\n",
"class A {}\n"
],
"replacementTests": []
}

View File

@@ -0,0 +1,7 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n"
],
"replacementTests": []
}

View File

@@ -0,0 +1,5 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": []
}

View File

@@ -0,0 +1,17 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n",
"class B {}\n"
],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
},
{
"inputLines": "class B {}\n",
"outputLines": "class B {}\n"
}
]
}

View File

@@ -0,0 +1,5 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": []
}

View File

@@ -0,0 +1,14 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": [
{
"inputLines": "class B {}\n",
"outputLines": "class B {}\n"
},
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.7.0</version>
<version>0.9.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -37,7 +37,15 @@
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>
<scope>test</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>documentation-support</artifactId>
<!-- This dependency is declared only as a hint to Maven that
compilation depends on it; see the `maven-compiler-plugin`'s
`annotationProcessorPaths` configuration below. -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
@@ -213,6 +221,11 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>documentation-support</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
@@ -226,6 +239,7 @@
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleCompiler</arg>
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
</compilerArgs>
</configuration>
</plugin>
@@ -243,4 +257,33 @@
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>docgen</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>add-test-source</id>
<goals>
<goal>add-test-source</goal>
</goals>
<phase>generate-test-sources</phase>
<configuration>
<sources>
<source>src/test/resources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,96 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags unnecessarily nested usage of methods that implement an
* associative operation.
*
* <p>The arguments to such methods can be flattened without affecting semantics, while making the
* code more readable.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"These methods implement an associative operation, so the list of operands can be flattened",
link = BUG_PATTERNS_BASE_URL + "AssociativeMethodInvocation",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AssociativeMethodInvocation extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> ITERABLE = Suppliers.typeFromClass(Iterable.class);
private static final ImmutableSet<Matcher<ExpressionTree>> ASSOCIATIVE_OPERATIONS =
ImmutableSet.of(
allOf(
staticMethod().onClass(Suppliers.typeFromClass(Matchers.class)).named("allOf"),
toType(MethodInvocationTree.class, not(hasArgumentOfType(ITERABLE)))),
allOf(
staticMethod().onClass(Suppliers.typeFromClass(Matchers.class)).named("anyOf"),
toType(MethodInvocationTree.class, not(hasArgumentOfType(ITERABLE)))),
staticMethod().onClass(Suppliers.typeFromClass(Refaster.class)).named("anyOf"));
/** Instantiates a new {@link AssociativeMethodInvocation} instance. */
public AssociativeMethodInvocation() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (tree.getArguments().isEmpty()) {
/* Absent any arguments, there is nothing to simplify. */
return Description.NO_MATCH;
}
for (Matcher<ExpressionTree> matcher : ASSOCIATIVE_OPERATIONS) {
if (matcher.matches(tree, state)) {
SuggestedFix.Builder fix = SuggestedFix.builder();
for (ExpressionTree arg : tree.getArguments()) {
if (matcher.matches(arg, state)) {
MethodInvocationTree invocation = (MethodInvocationTree) arg;
fix.merge(
invocation.getArguments().isEmpty()
? SuggestedFixes.removeElement(arg, tree.getArguments(), state)
: SourceCode.unwrapMethodInvocation(invocation, state));
}
}
return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix.build());
}
}
return Description.NO_MATCH;
}
private static Matcher<MethodInvocationTree> hasArgumentOfType(Supplier<Type> type) {
return (tree, state) ->
tree.getArguments().stream()
.anyMatch(arg -> ASTHelpers.isSubtype(ASTHelpers.getType(arg), type.get(state), state));
}
}

View File

@@ -0,0 +1,175 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isVariable;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.returnStatement;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.BlockTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symbol;
import java.util.List;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags unnecessary local variable assignments preceding a return
* statement.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Variable assignment is redundant; value can be returned directly",
link = BUG_PATTERNS_BASE_URL + "DirectReturn",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<StatementTree> VARIABLE_RETURN = returnStatement(isVariable());
private static final Matcher<ExpressionTree> MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE =
allOf(
not(toType(MethodInvocationTree.class, argument(0, isSameType(Class.class.getName())))),
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
/** Instantiates a new {@link DirectReturn} instance. */
public DirectReturn() {}
@Override
public Description matchBlock(BlockTree tree, VisitorState state) {
List<? extends StatementTree> statements = tree.getStatements();
if (statements.size() < 2) {
return Description.NO_MATCH;
}
StatementTree finalStatement = statements.get(statements.size() - 1);
if (!VARIABLE_RETURN.matches(finalStatement, state)) {
return Description.NO_MATCH;
}
Symbol variableSymbol = ASTHelpers.getSymbol(((ReturnTree) finalStatement).getExpression());
StatementTree precedingStatement = statements.get(statements.size() - 2);
return tryMatchAssignment(variableSymbol, precedingStatement)
.filter(
resultExpr ->
canInlineToReturnStatement(resultExpr, state)
&& !isIdentifierSymbolReferencedInAssociatedFinallyBlock(variableSymbol, state))
.map(
resultExpr ->
describeMatch(
precedingStatement,
SuggestedFix.builder()
.replace(
precedingStatement,
String.format("return %s;", SourceCode.treeToString(resultExpr, state)))
.delete(finalStatement)
.build()))
.orElse(Description.NO_MATCH);
}
private static Optional<ExpressionTree> tryMatchAssignment(Symbol targetSymbol, Tree tree) {
if (tree instanceof ExpressionStatementTree) {
return tryMatchAssignment(targetSymbol, ((ExpressionStatementTree) tree).getExpression());
}
if (tree instanceof AssignmentTree) {
AssignmentTree assignment = (AssignmentTree) tree;
return targetSymbol.equals(ASTHelpers.getSymbol(assignment.getVariable()))
? Optional.of(assignment.getExpression())
: Optional.empty();
}
if (tree instanceof VariableTree) {
VariableTree declaration = (VariableTree) tree;
return declaration.getModifiers().getAnnotations().isEmpty()
&& targetSymbol.equals(ASTHelpers.getSymbol(declaration))
? Optional.ofNullable(declaration.getInitializer())
: Optional.empty();
}
return Optional.empty();
}
/**
* Tells whether inlining the given expression to the associated return statement can likely be
* done without changing the expression's return type.
*
* <p>Inlining an expression generally does not change its return type, but in rare cases the
* operation may have a functional impact. The sole case considered here is the inlining of a
* Mockito mock or spy construction without an explicit type. In such a case the type created
* depends on context, such as the method's return type.
*/
private static boolean canInlineToReturnStatement(
ExpressionTree expressionTree, VisitorState state) {
return !MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE.matches(expressionTree, state)
|| MoreASTHelpers.findMethodExitedOnReturn(state)
.filter(m -> MoreASTHelpers.areSameType(expressionTree, m.getReturnType(), state))
.isPresent();
}
/**
* Tells whether the given identifier {@link Symbol} is referenced in a {@code finally} block that
* is executed <em>after</em> control flow returns from the {@link VisitorState#getPath() current
* location}.
*/
private static boolean isIdentifierSymbolReferencedInAssociatedFinallyBlock(
Symbol symbol, VisitorState state) {
return Streams.zip(
Streams.stream(state.getPath()).skip(1),
Streams.stream(state.getPath()),
(tree, child) -> {
if (!(tree instanceof TryTree)) {
return null;
}
BlockTree finallyBlock = ((TryTree) tree).getFinallyBlock();
return !child.equals(finallyBlock) ? finallyBlock : null;
})
.anyMatch(finallyBlock -> referencesIdentifierSymbol(symbol, finallyBlock));
}
private static boolean referencesIdentifierSymbol(Symbol symbol, @Nullable BlockTree tree) {
return Boolean.TRUE.equals(
new TreeScanner<Boolean, @Nullable Void>() {
@Override
public Boolean visitIdentifier(IdentifierTree node, @Nullable Void unused) {
return symbol.equals(ASTHelpers.getSymbol(node));
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return Boolean.TRUE.equals(r1) || Boolean.TRUE.equals(r2);
}
}.scan(tree, null));
}
}

View File

@@ -152,7 +152,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
String value = ASTHelpers.constValue(sourceLine, String.class);
if (value == null) {
return Optional.empty();
}

View File

@@ -82,10 +82,8 @@ public final class FluxFlatMapUsage extends BugChecker
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
SuggestedFix concurrencyCapFix =
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build();
SuggestedFix.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME);
Description.Builder description = buildDescription(tree);

View File

@@ -0,0 +1,100 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.CONCURRENCY;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Position;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
/**
* A {@link BugChecker} that flags {@link reactor.core.publisher.Flux} operator usages that may
* implicitly cause the calling thread to be blocked.
*
* <p>Note that the methods flagged here are not themselves blocking, but iterating over the
* resulting {@link Iterable} or {@link Stream} may be.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid iterating over `Flux`es in an implicitly blocking manner",
link = BUG_PATTERNS_BASE_URL + "FluxImplicitBlock",
linkType = CUSTOM,
severity = WARNING,
tags = {CONCURRENCY, PERFORMANCE})
public final class FluxImplicitBlock extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> FLUX_WITH_IMPLICIT_BLOCK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf("toIterable", "toStream")
.withNoParameters();
private static final Supplier<Type> STREAM = Suppliers.typeFromString(Stream.class.getName());
/** Instantiates a new {@link FluxImplicitBlock} instance. */
public FluxImplicitBlock() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!FLUX_WITH_IMPLICIT_BLOCK.matches(tree, state)) {
return Description.NO_MATCH;
}
Description.Builder description =
buildDescription(tree).addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()));
if (ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)) {
description.addFix(
suggestBlockingElementCollection(
tree, "com.google.common.collect.ImmutableList.toImmutableList", state));
}
description.addFix(
suggestBlockingElementCollection(tree, "java.util.stream.Collectors.toList", state));
return description.build();
}
private static SuggestedFix suggestBlockingElementCollection(
MethodInvocationTree tree, String fullyQualifiedCollectorMethod, VisitorState state) {
SuggestedFix.Builder importSuggestion = SuggestedFix.builder();
String replacementMethodInvocation =
SuggestedFixes.qualifyStaticImport(fullyQualifiedCollectorMethod, importSuggestion, state);
boolean isStream =
ASTHelpers.isSubtype(ASTHelpers.getResultType(tree), STREAM.get(state), state);
String replacement =
String.format(
".collect(%s()).block()%s", replacementMethodInvocation, isStream ? ".stream()" : "");
return importSuggestion.merge(replaceMethodInvocation(tree, replacement, state)).build();
}
private static SuggestedFix.Builder replaceMethodInvocation(
MethodInvocationTree tree, String replacement, VisitorState state) {
int startPosition = state.getEndPosition(ASTHelpers.getReceiver(tree));
int endPosition = state.getEndPosition(tree);
checkState(
startPosition != Position.NOPOS && endPosition != Position.NOPOS,
"Cannot locate method to be replaced in source code");
return SuggestedFix.builder().replace(startPosition, endPosition, replacement);
}
}

View File

@@ -35,6 +35,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
// XXX: Consider detecting cases where a flagged expression is passed to a method, and where removal
// of the identity conversion would cause a different method overload to be selected. Depending on
// the target method such a modification may change the code's semantics or performance.
// XXX: Also flag `Stream#map`, `Mono#map` and `Flux#map` invocations where the given transformation
// is effectively the identity operation.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid or clarify identity conversions",

View File

@@ -9,7 +9,6 @@ import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.not;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
@@ -25,15 +24,11 @@ import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
import tech.picnic.errorprone.bugpatterns.util.ConflictDetection;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check that enforces that test classes:
@@ -90,7 +85,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
tryCanonicalizeMethodName(symbol)
.ifPresent(
newName ->
findMethodRenameBlocker(symbol, newName, state)
ConflictDetection.findMethodRenameBlocker(symbol, newName, state)
.ifPresentOrElse(
blocker -> reportMethodRenameBlocker(tree, blocker, state),
() -> fixBuilder.merge(SuggestedFixes.renameMethod(tree, newName, state))));
@@ -106,61 +101,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
.build());
}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration.
* <li>Whether the rename would cause a method in a superclass to be overridden.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(
MethodSymbol method, String newName, VisitorState state) {
if (isExistingMethodName(method.owner.type, newName, state)) {
return Optional.of(
String.format(
"a method named `%s` is already defined in this class or a supertype", newName));
}
if (isSimpleNameStaticallyImported(newName, state)) {
return Optional.of(String.format("`%s` is already statically imported", newName));
}
if (!isValidIdentifier(newName)) {
return Optional.of(String.format("`%s` is not a valid identifier", newName));
}
return Optional.empty();
}
private static boolean isExistingMethodName(Type clazz, String name, VisitorState state) {
return ASTHelpers.matchingMethods(state.getName(name), method -> true, clazz, state.getTypes())
.findAny()
.isPresent();
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(Symbol symbol) {
private static Optional<String> tryCanonicalizeMethodName(MethodSymbol symbol) {
return Optional.of(symbol.getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))

View File

@@ -0,0 +1,307 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.classLiteral;
import static com.google.errorprone.matchers.Matchers.hasArguments;
import static com.google.errorprone.matchers.Matchers.isPrimitiveOrBoxedPrimitiveType;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.HAS_METHOD_SOURCE;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.getMethodSourceFactoryNames;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags JUnit tests with a {@link
* org.junit.jupiter.params.provider.MethodSource} annotation that can be replaced with an
* equivalent {@link org.junit.jupiter.params.provider.ValueSource} annotation.
*/
// XXX: Where applicable, also flag `@MethodSource` annotations that reference multiple value
// factory methods (or that repeat the same value factory method multiple times).
// XXX: Support inlining of overloaded value factory methods.
// XXX: Support inlining of value factory methods referenced by multiple `@MethodSource`
// annotations.
// XXX: Support value factory return expressions of the form `Stream.of(a, b,
// c).map(Arguments::argument)`.
// XXX: Support simplification of test methods that accept additional injected parameters such as
// `TestInfo`; such parameters should be ignored for the purpose of this check.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `@ValueSource` over a `@MethodSource` where possible and reasonable",
linkType = CUSTOM,
link = BUG_PATTERNS_BASE_URL + "JUnitValueSource",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class JUnitValueSource extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> SUPPORTED_VALUE_FACTORY_VALUES =
anyOf(
isArrayArgumentValueCandidate(),
toType(
MethodInvocationTree.class,
allOf(
staticMethod()
.onClass("org.junit.jupiter.params.provider.Arguments")
.namedAnyOf("arguments", "of"),
argumentCount(1),
argument(0, isArrayArgumentValueCandidate()))));
private static final Matcher<ExpressionTree> ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
isSingleDimensionArrayCreationWithAllElementsMatching(SUPPORTED_VALUE_FACTORY_VALUES);
private static final Matcher<ExpressionTree> ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
toType(
MethodInvocationTree.class,
allOf(
staticMethod()
.onClassAny(
Stream.class.getName(),
IntStream.class.getName(),
LongStream.class.getName(),
DoubleStream.class.getName(),
List.class.getName(),
Set.class.getName(),
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableSet")
.named("of"),
hasArguments(AT_LEAST_ONE, anything()),
hasArguments(ALL, SUPPORTED_VALUE_FACTORY_VALUES)));
private static final Matcher<MethodTree> IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER =
methodHasParameters(
anyOf(
isPrimitiveOrBoxedPrimitiveType(),
isSameType(String.class),
isSameType(state -> state.getSymtab().classType)));
/** Instantiates a new {@link JUnitValueSource} instance. */
public JUnitValueSource() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER.matches(tree, state)) {
return Description.NO_MATCH;
}
Type parameterType = ASTHelpers.getType(Iterables.getOnlyElement(tree.getParameters()));
return findMethodSourceAnnotation(tree, state)
.flatMap(
methodSourceAnnotation ->
getSoleLocalFactoryName(methodSourceAnnotation, tree)
.filter(factory -> !hasSiblingReferencingValueFactory(tree, factory, state))
.flatMap(factory -> findSiblingWithName(tree, factory, state))
.flatMap(
factoryMethod ->
tryConstructValueSourceFix(
parameterType, methodSourceAnnotation, factoryMethod, state))
.map(fix -> describeMatch(methodSourceAnnotation, fix)))
.orElse(Description.NO_MATCH);
}
/**
* Returns the name of the value factory method pointed to by the given {@code @MethodSource}
* annotation, if it (a) is the only one and (b) is a method in the same class as the annotated
* method.
*/
private static Optional<String> getSoleLocalFactoryName(
AnnotationTree methodSourceAnnotation, MethodTree method) {
return getElementIfSingleton(getMethodSourceFactoryNames(methodSourceAnnotation, method))
.filter(name -> name.indexOf('#') < 0);
}
/**
* Tells whether the given method has a sibling method in the same class that depends on the
* specified value factory method.
*/
private static boolean hasSiblingReferencingValueFactory(
MethodTree tree, String valueFactory, VisitorState state) {
return findMatchingSibling(tree, m -> hasValueFactory(m, valueFactory, state), state)
.isPresent();
}
private static Optional<MethodTree> findSiblingWithName(
MethodTree tree, String methodName, VisitorState state) {
return findMatchingSibling(tree, m -> m.getName().contentEquals(methodName), state);
}
private static Optional<MethodTree> findMatchingSibling(
MethodTree tree, Predicate<? super MethodTree> predicate, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(not(tree::equals))
.filter(predicate)
.findFirst();
}
private static boolean hasValueFactory(
MethodTree tree, String valueFactoryMethodName, VisitorState state) {
return findMethodSourceAnnotation(tree, state).stream()
.anyMatch(
annotation ->
getMethodSourceFactoryNames(annotation, tree).contains(valueFactoryMethodName));
}
private static Optional<AnnotationTree> findMethodSourceAnnotation(
MethodTree tree, VisitorState state) {
return HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes().stream().findFirst();
}
private static Optional<SuggestedFix> tryConstructValueSourceFix(
Type parameterType,
AnnotationTree methodSourceAnnotation,
MethodTree valueFactoryMethod,
VisitorState state) {
return getSingleReturnExpression(valueFactoryMethod)
.flatMap(expression -> tryExtractValueSourceAttributeValue(expression, state))
.map(
valueSourceAttributeValue ->
SuggestedFix.builder()
.addImport("org.junit.jupiter.params.provider.ValueSource")
.replace(
methodSourceAnnotation,
String.format(
"@ValueSource(%s = %s)",
toValueSourceAttributeName(parameterType), valueSourceAttributeValue))
.delete(valueFactoryMethod)
.build());
}
// XXX: This pattern also occurs a few times inside Error Prone; contribute upstream.
private static Optional<ExpressionTree> getSingleReturnExpression(MethodTree methodTree) {
List<ExpressionTree> returnExpressions = new ArrayList<>();
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitClass(ClassTree node, @Nullable Void unused) {
/* Ignore `return` statements inside anonymous/local classes. */
return null;
}
@Override
public @Nullable Void visitReturn(ReturnTree node, @Nullable Void unused) {
returnExpressions.add(node.getExpression());
return super.visitReturn(node, unused);
}
@Override
public @Nullable Void visitLambdaExpression(
LambdaExpressionTree node, @Nullable Void unused) {
/* Ignore `return` statements inside lambda expressions. */
return null;
}
}.scan(methodTree, null);
return getElementIfSingleton(returnExpressions);
}
private static Optional<String> tryExtractValueSourceAttributeValue(
ExpressionTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments;
if (ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
arguments = ((MethodInvocationTree) tree).getArguments();
} else if (ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
arguments = ((NewArrayTree) tree).getInitializers();
} else {
return Optional.empty();
}
/*
* Join the values into a comma-separated string, unwrapping `Arguments` factory method
* invocations if applicable.
*/
return Optional.of(
arguments.stream()
.map(
arg ->
arg instanceof MethodInvocationTree
? Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments())
: arg)
.map(argument -> SourceCode.treeToString(argument, state))
.collect(joining(", ")))
.map(value -> arguments.size() > 1 ? String.format("{%s}", value) : value);
}
private static String toValueSourceAttributeName(Type type) {
String typeString = type.tsym.name.toString();
switch (typeString) {
case "Class":
return "classes";
case "Character":
return "chars";
case "Integer":
return "ints";
default:
return typeString.toLowerCase(Locale.ROOT) + 's';
}
}
private static <T> Optional<T> getElementIfSingleton(Collection<T> collection) {
return Optional.of(collection)
.filter(elements -> elements.size() == 1)
.map(Iterables::getOnlyElement);
}
private static Matcher<ExpressionTree> isSingleDimensionArrayCreationWithAllElementsMatching(
Matcher<? super ExpressionTree> elementMatcher) {
return (tree, state) -> {
if (!(tree instanceof NewArrayTree)) {
return false;
}
NewArrayTree newArray = (NewArrayTree) tree;
return newArray.getDimensions().isEmpty()
&& !newArray.getInitializers().isEmpty()
&& newArray.getInitializers().stream()
.allMatch(element -> elementMatcher.matches(element, state));
};
}
private static Matcher<ExpressionTree> isArrayArgumentValueCandidate() {
return anyOf(classLiteral(anything()), (tree, state) -> ASTHelpers.constValue(tree) != null);
}
}

View File

@@ -0,0 +1,86 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isVariable;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
/**
* A {@link BugChecker} that flags the use of {@link org.mockito.Mockito#mock(Class)} and {@link
* org.mockito.Mockito#spy(Class)} where instead the type to be mocked or spied can be derived from
* context.
*/
// XXX: This check currently does not flag method invocation arguments. When adding support for
// this, consider that in some cases the type to be mocked or spied must be specified explicitly so
// as to disambiguate between method overloads.
// XXX: This check currently does not flag (implicit or explicit) lambda return expressions.
// XXX: This check currently does not drop suppressions that become obsolete after the
// suggested fix is applied; consider adding support for this.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Don't unnecessarily pass a type to Mockito's `mock(Class)` and `spy(Class)` methods",
link = BUG_PATTERNS_BASE_URL + "MockitoMockClassReference",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoMockClassReference extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE =
allOf(
argument(0, allOf(isSameType(Class.class.getName()), not(isVariable()))),
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
/** Instantiates a new {@link MockitoMockClassReference} instance. */
public MockitoMockClassReference() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE.matches(tree, state)
|| !isTypeDerivableFromContext(tree, state)) {
return Description.NO_MATCH;
}
List<? extends ExpressionTree> arguments = tree.getArguments();
return describeMatch(tree, SuggestedFixes.removeElement(arguments.get(0), arguments, state));
}
private static boolean isTypeDerivableFromContext(MethodInvocationTree tree, VisitorState state) {
Tree parent = state.getPath().getParentPath().getLeaf();
switch (parent.getKind()) {
case VARIABLE:
return !ASTHelpers.hasNoExplicitType((VariableTree) parent, state)
&& MoreASTHelpers.areSameType(tree, parent, state);
case ASSIGNMENT:
return MoreASTHelpers.areSameType(tree, parent, state);
case RETURN:
return MoreASTHelpers.findMethodExitedOnReturn(state)
.filter(m -> MoreASTHelpers.areSameType(tree, m.getReturnType(), state))
.isPresent();
default:
return false;
}
}
}

View File

@@ -7,6 +7,7 @@ import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyMethod;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
import static com.google.errorprone.matchers.Matchers.isSameType;
@@ -67,9 +68,7 @@ public final class RedundantStringConversion extends BugChecker
private static final String EXTRA_STRING_CONVERSION_METHODS_FLAG =
"RedundantStringConversion:ExtraConversionMethods";
@SuppressWarnings("UnnecessaryLambda")
private static final Matcher<ExpressionTree> ANY_EXPR = (t, s) -> true;
private static final Matcher<ExpressionTree> ANY_EXPR = anything();
private static final Matcher<ExpressionTree> LOCALE = isSameType(Locale.class);
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> STRING = isSameType(String.class);

View File

@@ -74,9 +74,13 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
isSameType("java.time.ZoneId"),
isSameType("java.util.Locale"),
isSameType("java.util.TimeZone"),
isSameType("jakarta.servlet.http.HttpServletRequest"),
isSameType("jakarta.servlet.http.HttpServletResponse"),
isSameType("javax.servlet.http.HttpServletRequest"),
isSameType("javax.servlet.http.HttpServletResponse"),
isSameType("org.springframework.http.HttpMethod"),
isSameType("org.springframework.ui.Model"),
isSameType("org.springframework.validation.BindingResult"),
isSameType("org.springframework.web.context.request.NativeWebRequest"),
isSameType("org.springframework.web.context.request.WebRequest"),
isSameType("org.springframework.web.server.ServerWebExchange"),

View File

@@ -0,0 +1,75 @@
package tech.picnic.errorprone.bugpatterns.util;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
/** A set of helper methods for detecting conflicts that would be caused when applying fixes. */
public final class ConflictDetection {
private ConflictDetection() {}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration in its class or a supertype.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*
* @param method The method considered for renaming.
* @param newName The newly proposed name for the method.
* @param state The {@link VisitorState} to use when searching for blockers.
* @return A human-readable argument against assigning the proposed name to the given method, or
* {@link Optional#empty()} if no blocker was found.
*/
public static Optional<String> findMethodRenameBlocker(
MethodSymbol method, String newName, VisitorState state) {
if (isExistingMethodName(method.owner.type, newName, state)) {
return Optional.of(
String.format(
"a method named `%s` is already defined in this class or a supertype", newName));
}
if (isSimpleNameStaticallyImported(newName, state)) {
return Optional.of(String.format("`%s` is already statically imported", newName));
}
if (!isValidIdentifier(newName)) {
return Optional.of(String.format("`%s` is not a valid identifier", newName));
}
return Optional.empty();
}
private static boolean isExistingMethodName(Type clazz, String name, VisitorState state) {
return ASTHelpers.matchingMethods(state.getName(name), method -> true, clazz, state.getTypes())
.findAny()
.isPresent();
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
}

View File

@@ -5,8 +5,12 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Optional;
/**
* A collection of helper methods for working with the AST.
@@ -46,4 +50,33 @@ public final class MoreASTHelpers {
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}
/**
* Returns the {@link MethodTree} from which control flow would exit if there would be a {@code
* return} statement at the given {@link VisitorState}'s current {@link VisitorState#getPath()
* path}.
*
* @param state The {@link VisitorState} from which to derive the AST location of interest.
* @return A {@link MethodTree}, unless the {@link VisitorState}'s path does not point to an AST
* node located inside a method, or if the (hypothetical) {@code return} statement would exit
* a lambda expression instead.
*/
public static Optional<MethodTree> findMethodExitedOnReturn(VisitorState state) {
return Optional.ofNullable(state.findEnclosing(LambdaExpressionTree.class, MethodTree.class))
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast);
}
/**
* Tells whether the given trees are of the same type, after type erasure.
*
* @param treeA The first tree of interest.
* @param treeB The second tree of interest.
* @param state The {@link VisitorState} describing the context in which the given trees were
* found.
* @return Whether the specified trees have the same erased types.
*/
public static boolean areSameType(Tree treeA, Tree treeB, VisitorState state) {
return ASTHelpers.isSameType(ASTHelpers.getType(treeA), ASTHelpers.getType(treeB), state);
}
}

View File

@@ -1,6 +1,6 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
@@ -9,7 +9,7 @@ import static java.util.Objects.requireNonNullElse;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.matchers.AnnotationMatcherUtils;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
@@ -55,25 +55,59 @@ public final class MoreJUnitMatchers {
* Returns the names of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* <p>This method differs from {@link #getMethodSourceFactoryDescriptors(AnnotationTree,
* MethodTree)} in that it drops any parenthesized method parameter type enumerations. That is,
* method descriptors such as {@code factoryMethod()} and {@code factoryMethod(java.lang.String)}
* are both simplified to just {@code factoryMethod}. This also means that the returned method
* names may not unambiguously reference a single value factory method; in such a case JUnit will
* throw an error at runtime.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method names.
* @return One or more value factory names.
* @param method The method on which the annotation is located.
* @return One or more value factory descriptors, in the order defined.
* @see #getMethodSourceFactoryDescriptors(AnnotationTree, MethodTree)
*/
static ImmutableSet<String> getMethodSourceFactoryNames(
// XXX: Drop this method in favour of `#getMethodSourceFactoryDescriptors`. That will require
// callers to either explicitly drop information, or perform a more advanced analysis.
public static ImmutableList<String> getMethodSourceFactoryNames(
AnnotationTree methodSourceAnnotation, MethodTree method) {
return getMethodSourceFactoryDescriptors(methodSourceAnnotation, method).stream()
.map(
descriptor -> {
int index = descriptor.indexOf('(');
return index < 0 ? descriptor : descriptor.substring(0, index);
})
.collect(toImmutableList());
}
/**
* Returns the descriptors of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method
* descriptors.
* @param method The method on which the annotation is located.
* @return One or more value factory descriptors, in the order defined.
* @see #getMethodSourceFactoryNames(AnnotationTree, MethodTree)
*/
// XXX: Rather than strings, have this method return instances of a value type capable of
// resolving the value factory method pointed to.
public static ImmutableList<String> getMethodSourceFactoryDescriptors(
AnnotationTree methodSourceAnnotation, MethodTree method) {
String methodName = method.getName().toString();
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value");
if (!(value instanceof NewArrayTree)) {
return ImmutableSet.of(toMethodSourceFactoryName(value, methodName));
return ImmutableList.of(toMethodSourceFactoryDescriptor(value, methodName));
}
return ((NewArrayTree) value)
.getInitializers().stream()
.map(name -> toMethodSourceFactoryName(name, methodName))
.collect(toImmutableSet());
.map(name -> toMethodSourceFactoryDescriptor(name, methodName))
.collect(toImmutableList());
}
private static String toMethodSourceFactoryName(
private static String toMethodSourceFactoryDescriptor(
@Nullable ExpressionTree tree, String annotatedMethodName) {
return requireNonNullElse(
Strings.emptyToNull(ASTHelpers.constValue(tree, String.class)), annotatedMethodName);

View File

@@ -1,12 +1,22 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.sun.tools.javac.parser.Tokens.TokenKind.RPAREN;
import static com.sun.tools.javac.util.Position.NOPOS;
import static java.util.stream.Collectors.joining;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.util.ErrorProneToken;
import com.google.errorprone.util.ErrorProneTokens;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.Position;
import java.util.Optional;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
@@ -59,4 +69,57 @@ public final class SourceCode {
whitespaceEndPos == -1 ? sourceCode.length() : whitespaceEndPos,
"");
}
/**
* Creates a {@link SuggestedFix} for the replacement of the given {@link MethodInvocationTree}
* with just the arguments to the method invocation, effectively "unwrapping" the method
* invocation.
*
* <p>For example, given the method invocation {@code foo.bar(1, 2, 3)}, this method will return a
* {@link SuggestedFix} that replaces the method invocation with {@code 1, 2, 3}.
*
* <p>This method aims to preserve the original formatting of the method invocation, including
* whitespace and comments.
*
* @param tree The AST node to be unwrapped.
* @param state A {@link VisitorState} describing the context in which the given {@link
* MethodInvocationTree} is found.
* @return A non-{@code null} {@link SuggestedFix}.
*/
public static SuggestedFix unwrapMethodInvocation(MethodInvocationTree tree, VisitorState state) {
CharSequence sourceCode = state.getSourceCode();
int startPosition = state.getEndPosition(tree.getMethodSelect());
int endPosition = state.getEndPosition(tree);
if (sourceCode == null || startPosition == Position.NOPOS || endPosition == Position.NOPOS) {
return unwrapMethodInvocationDroppingWhitespaceAndComments(tree, state);
}
ImmutableList<ErrorProneToken> tokens =
ErrorProneTokens.getTokens(
sourceCode.subSequence(startPosition, endPosition).toString(), state.context);
Optional<Integer> leftParenPosition =
tokens.stream().findFirst().map(t -> startPosition + t.endPos());
Optional<Integer> rightParenPosition =
Streams.findLast(tokens.stream().filter(t -> t.kind() == RPAREN))
.map(t -> startPosition + t.pos());
if (leftParenPosition.isEmpty() || rightParenPosition.isEmpty()) {
return unwrapMethodInvocationDroppingWhitespaceAndComments(tree, state);
}
return SuggestedFix.replace(
tree,
sourceCode
.subSequence(leftParenPosition.orElseThrow(), rightParenPosition.orElseThrow())
.toString());
}
@VisibleForTesting
static SuggestedFix unwrapMethodInvocationDroppingWhitespaceAndComments(
MethodInvocationTree tree, VisitorState state) {
return SuggestedFix.replace(
tree,
tree.getArguments().stream().map(arg -> treeToString(arg, state)).collect(joining(", ")));
}
}

View File

@@ -16,15 +16,18 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.MapAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -220,6 +223,19 @@ final class AssertJMapRules {
}
}
static final class AssertThatMapContainsOnlyKeys<K, V> {
@BeforeTemplate
AbstractCollectionAssert<?, Collection<? extends K>, K, ?> before(Map<K, V> map, Set<K> keys) {
return assertThat(map.keySet()).hasSameElementsAs(keys);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, Set<K> keys) {
return assertThat(map).containsOnlyKeys(keys);
}
}
static final class AssertThatMapContainsValue<K, V> {
@BeforeTemplate
AbstractBooleanAssert<?> before(Map<K, V> map, V value) {

View File

@@ -1,11 +1,16 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractStringAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -89,4 +94,30 @@ final class AssertJStringRules {
return assertThat(string).doesNotMatch(regex);
}
}
static final class AssertThatPathContent {
@BeforeTemplate
AbstractStringAssert<?> before(Path path, Charset charset) throws IOException {
return assertThat(Files.readString(path, charset));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractStringAssert<?> after(Path path, Charset charset) {
return assertThat(path).content(charset);
}
}
static final class AssertThatPathContentUtf8 {
@BeforeTemplate
AbstractStringAssert<?> before(Path path) throws IOException {
return assertThat(Files.readString(path));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractStringAssert<?> after(Path path) {
return assertThat(path).content(UTF_8);
}
}
}

View File

@@ -0,0 +1,53 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChooser;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to {@link com.google.errorprone.bugpatterns.BugChecker} classes. */
@OnlineDocumentation
final class BugCheckerRules {
private BugCheckerRules() {}
/**
* Avoid calling {@link BugCheckerRefactoringTestHelper#setFixChooser(FixChooser)} or {@link
* BugCheckerRefactoringTestHelper#setImportOrder(String)} with their respective default values.
*/
static final class BugCheckerRefactoringTestHelperIdentity {
@BeforeTemplate
BugCheckerRefactoringTestHelper before(BugCheckerRefactoringTestHelper helper) {
return Refaster.anyOf(
helper.setFixChooser(FixChoosers.FIRST), helper.setImportOrder("static-first"));
}
@AfterTemplate
BugCheckerRefactoringTestHelper after(BugCheckerRefactoringTestHelper helper) {
return helper;
}
}
/**
* Prefer {@link BugCheckerRefactoringTestHelper.ExpectOutput#expectUnchanged()} over repeating
* the input.
*/
// XXX: This rule assumes that the full source code is specified as a single string, e.g. using a
// text block. Support for multi-line source code input would require a `BugChecker`
// implementation instead.
static final class BugCheckerRefactoringTestHelperAddInputLinesExpectUnchanged {
@BeforeTemplate
BugCheckerRefactoringTestHelper before(
BugCheckerRefactoringTestHelper helper, String path, String source) {
return helper.addInputLines(path, source).addOutputLines(path, source);
}
@AfterTemplate
BugCheckerRefactoringTestHelper after(
BugCheckerRefactoringTestHelper helper, String path, String source) {
return helper.addInputLines(path, source).expectUnchanged();
}
}
}

View File

@@ -6,6 +6,7 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.function.Function.identity;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
@@ -126,8 +127,8 @@ final class ImmutableMapRules {
}
/**
* Don't map a a stream's elements to map entries, only to subsequently collect them into an
* {@link ImmutableMap}. The collection can be performed directly.
* Don't map a stream's elements to map entries, only to subsequently collect them into an {@link
* ImmutableMap}. The collection can be performed directly.
*/
abstract static class StreamOfMapEntriesToImmutableMap<E, K, V> {
@Placeholder(allowsIdentity = true)
@@ -315,6 +316,48 @@ final class ImmutableMapRules {
}
}
/**
* Prefer creation of an immutable submap using {@link Maps#filterKeys(Map, Predicate)} over more
* contrived alternatives.
*/
abstract static class ImmutableMapCopyOfMapsFilterKeys<K, V> {
@Placeholder(allowsIdentity = true)
abstract boolean keyFilter(@MayOptionallyUse K key);
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return map.entrySet().stream()
.filter(e -> keyFilter(e.getKey()))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(Maps.filterKeys(map, k -> keyFilter(k)));
}
}
/**
* Prefer creation of an immutable submap using {@link Maps#filterValues(Map, Predicate)} over
* more contrived alternatives.
*/
abstract static class ImmutableMapCopyOfMapsFilterValues<K, V> {
@Placeholder(allowsIdentity = true)
abstract boolean valueFilter(@MayOptionallyUse V value);
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return map.entrySet().stream()
.filter(e -> valueFilter(e.getValue()))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(Maps.filterValues(map, v -> valueFilter(v)));
}
}
// XXX: Add a rule for this:
// Maps.transformValues(streamOfEntries.collect(groupBy(fun)), ImmutableMap::copyOf)
// ->
@@ -323,9 +366,4 @@ final class ImmutableMapRules {
// map.entrySet().stream().filter(keyPred).forEach(mapBuilder::put)
// ->
// mapBuilder.putAll(Maps.filterKeys(map, pred))
//
// map.entrySet().stream().filter(entry ->
// pred(entry.getKey())).collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
// ->
// ImmutableMap.copyOf(Maps.filterKeys(map, pred))
}

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -43,6 +45,21 @@ final class MapRules {
}
}
/** Prefer {@link Map#getOrDefault(Object, Object)} over more contrived alternatives. */
// XXX: Note that `requireNonNullElse` throws an NPE if the second argument is `null`, while the
// alternative does not.
static final class MapGetOrDefault<K, V, T> {
@BeforeTemplate
V before(Map<K, V> map, T key, V defaultValue) {
return requireNonNullElse(map.get(key), defaultValue);
}
@AfterTemplate
V after(Map<K, V> map, T key, V defaultValue) {
return map.getOrDefault(key, defaultValue);
}
}
/** Prefer {@link Map#isEmpty()} over more contrived alternatives. */
static final class MapIsEmpty<K, V> {
@BeforeTemplate

View File

@@ -21,11 +21,14 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
final class NullRules {
private NullRules() {}
/** Prefer the {@code ==} operator over {@link Objects#isNull(Object)}. */
/**
* Prefer the {@code ==} operator (with {@code null} as the second operand) over {@link
* Objects#isNull(Object)}.
*/
static final class IsNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Objects.isNull(object);
return Refaster.anyOf(null == object, Objects.isNull(object));
}
@AfterTemplate
@@ -34,11 +37,14 @@ final class NullRules {
}
}
/** Prefer the {@code !=} operator over {@link Objects#nonNull(Object)}. */
/**
* Prefer the {@code !=} operator (with {@code null} as the second operand) over {@link
* Objects#nonNull(Object)}.
*/
static final class IsNotNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Objects.nonNull(object);
return Refaster.anyOf(null != object, Objects.nonNull(object));
}
@AfterTemplate

View File

@@ -6,11 +6,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Preconditions;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster templates related to statements dealing with {@link Preconditions}. */
@@ -72,8 +74,22 @@ final class PreconditionsRules {
}
}
/** Prefer {@link Preconditions#checkNotNull(Object)} over more verbose alternatives. */
static final class CheckNotNull<T> {
/** Prefer {@link Objects#requireNonNull(Object)} over non-JDK alternatives. */
static final class RequireNonNull<T> {
@BeforeTemplate
T before(T object) {
return checkNotNull(object);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(T object) {
return requireNonNull(object);
}
}
/** Prefer {@link Objects#requireNonNull(Object)} over more verbose alternatives. */
static final class RequireNonNullStatement<T> {
@BeforeTemplate
void before(T object) {
if (object == null) {
@@ -84,12 +100,26 @@ final class PreconditionsRules {
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object) {
checkNotNull(object);
requireNonNull(object);
}
}
/** Prefer {@link Preconditions#checkNotNull(Object, Object)} over more verbose alternatives. */
static final class CheckNotNullWithMessage<T> {
/** Prefer {@link Objects#requireNonNull(Object, String)} over non-JDK alternatives. */
static final class RequireNonNullWithMessage<T> {
@BeforeTemplate
T before(T object, String message) {
return checkNotNull(object, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(T object, String message) {
return requireNonNull(object, message);
}
}
/** Prefer {@link Objects#requireNonNull(Object, String)} over more verbose alternatives. */
static final class RequireNonNullWithMessageStatement<T> {
@BeforeTemplate
void before(T object, String message) {
if (object == null) {
@@ -100,7 +130,7 @@ final class PreconditionsRules {
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object, String message) {
checkNotNull(object, message);
requireNonNull(object, message);
}
}

View File

@@ -1,14 +1,19 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.MoreCollectors.toOptional;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MoreCollectors;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -17,8 +22,11 @@ import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
@@ -27,6 +35,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -88,7 +97,7 @@ final class ReactorRules {
}
/** Prefer {@link Mono#justOrEmpty(Object)} over more contrived alternatives. */
static final class MonoJustOrEmpty<@Nullable T> {
static final class MonoJustOrEmptyObject<@Nullable T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.ofNullable(value));
@@ -101,9 +110,25 @@ final class ReactorRules {
}
/** Prefer {@link Mono#justOrEmpty(Optional)} over more verbose alternatives. */
static final class MonoJustOrEmptyOptional<T> {
@BeforeTemplate
Mono<T> before(Optional<T> optional) {
return Mono.just(optional).filter(Optional::isPresent).map(Optional::orElseThrow);
}
@AfterTemplate
Mono<T> after(Optional<T> optional) {
return Mono.justOrEmpty(optional);
}
}
/**
* Prefer {@link Mono#defer(Supplier) deferring} {@link Mono#justOrEmpty(Optional)} over more
* verbose alternatives.
*/
// XXX: If `optional` is a constant and effectively-final expression then the `Mono.defer` can be
// dropped. Should look into Refaster support for identifying this.
static final class MonoFromOptional<T> {
static final class MonoDeferMonoJustOrEmpty<T> {
@BeforeTemplate
@SuppressWarnings(
"MonoFromSupplier" /* `optional` may match a checked exception-throwing expression. */)
@@ -369,7 +394,8 @@ final class ReactorRules {
static final class MonoIdentity<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono) {
return mono.switchIfEmpty(Mono.empty());
return Refaster.anyOf(
mono.switchIfEmpty(Mono.empty()), mono.flux().next(), mono.flux().singleOrEmpty());
}
@BeforeTemplate
@@ -377,6 +403,13 @@ final class ReactorRules {
return mono.then();
}
// XXX: Replace this rule with an extension of the `IdentityConversion` rule, supporting
// `Stream#map`, `Mono#map` and `Flux#map`.
@BeforeTemplate
Mono<ImmutableList<T>> before3(Mono<ImmutableList<T>> mono) {
return mono.map(ImmutableList::copyOf);
}
@AfterTemplate
Mono<T> after(Mono<T> mono) {
return mono;
@@ -473,6 +506,11 @@ final class ReactorRules {
* Flux}.
*/
abstract static class MonoFlatMapToFlux<T, S> {
// XXX: It would be more expressive if this `@Placeholder` were replaced with a `Function<?
// super T, ? extends Mono<? extends S>>` parameter, so that compatible non-lambda expression
// arguments to `flatMapMany` are also matched. However, the type inferred for lambda and method
// reference expressions passed to `flatMapMany` appears to always be `Function<T, Publisher<?
// extends S>>`, which doesn't match. Find a solution.
@Placeholder(allowsIdentity = true)
abstract Mono<S> transformation(@MayOptionallyUse T value);
@@ -693,21 +731,21 @@ final class ReactorRules {
}
}
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/
/** Prefer {@link Mono#singleOptional()} over more contrived alternatives. */
// XXX: Consider creating a plugin that flags/discourages `Mono<Optional<T>>` method return
// types, just as we discourage nullable `Boolean`s and `Optional`s.
static final class MonoCollectToOptional<T> {
static final class MonoSingleOptional<T> {
@BeforeTemplate
Mono<Optional<T>> before(Mono<T> mono) {
return mono.map(Optional::of).defaultIfEmpty(Optional.empty());
return Refaster.anyOf(
mono.flux().collect(toOptional()),
mono.map(Optional::of).defaultIfEmpty(Optional.empty()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Mono<Optional<T>> after(Mono<T> mono) {
return mono.flux().collect(toOptional());
return mono.singleOptional();
}
}
@@ -800,6 +838,31 @@ final class ReactorRules {
}
}
/**
* Prefer {@link Flux#count()} followed by a conversion from {@code long} to {@code int} over
* collecting into a list and counting its elements.
*/
static final class FluxCountMapMathToIntExact<T> {
@BeforeTemplate
Mono<Integer> before(Flux<T> flux) {
return Refaster.anyOf(
flux.collect(toImmutableList())
.map(
Refaster.anyOf(
Collection::size,
List::size,
ImmutableCollection::size,
ImmutableList::size)),
flux.collect(toCollection(ArrayList::new))
.map(Refaster.anyOf(Collection::size, List::size)));
}
@AfterTemplate
Mono<Integer> after(Flux<T> flux) {
return flux.count().map(Math::toIntExact);
}
}
/**
* Prefer {@link Mono#doOnError(Class, Consumer)} over {@link Mono#doOnError(Predicate, Consumer)}
* where possible.
@@ -1113,6 +1176,40 @@ final class ReactorRules {
}
}
/**
* Prefer {@link Flux#collect(Collector)} with {@link ImmutableList#toImmutableList()} over
* alternatives that do not explicitly return an immutable collection.
*/
static final class FluxCollectToImmutableList<T> {
@BeforeTemplate
Mono<List<T>> before(Flux<T> flux) {
return flux.collectList();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Mono<ImmutableList<T>> after(Flux<T> flux) {
return flux.collect(toImmutableList());
}
}
/**
* Prefer {@link Flux#collect(Collector)} with {@link ImmutableSet#toImmutableSet()} over more
* contrived alternatives.
*/
static final class FluxCollectToImmutableSet<T> {
@BeforeTemplate
Mono<ImmutableSet<T>> before(Flux<T> flux) {
return flux.collect(toImmutableList()).map(ImmutableSet::copyOf);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Mono<ImmutableSet<T>> after(Flux<T> flux) {
return flux.collect(toImmutableSet());
}
}
/** Prefer {@link reactor.util.context.Context#empty()}} over more verbose alternatives. */
// XXX: Consider introducing an `IsEmpty` matcher that identifies a wide range of guaranteed-empty
// `Collection` and `Map` expressions.
@@ -1167,12 +1264,14 @@ final class ReactorRules {
}
}
/** Don't unnecessarily call {@link StepVerifier.Step#expectNext(Object[])}. */
static final class StepVerifierStepExpectNextEmpty<T> {
/** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */
// XXX: Given an `IsEmpty` matcher that identifies a wide range of guaranteed-empty `Iterable`
// expressions, consider also simplifying `step.expectNextSequence(someEmptyIterable)`.
static final class StepVerifierStepIdentity<T> {
@BeforeTemplate
@SuppressWarnings("unchecked")
StepVerifier.Step<T> before(StepVerifier.Step<T> step) {
return step.expectNext();
return Refaster.anyOf(step.expectNext(), step.expectNextCount(0));
}
@AfterTemplate

View File

@@ -10,6 +10,7 @@ import com.google.common.collect.Streams;
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.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -19,10 +20,14 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference;
/** Refaster rules related to expressions dealing with {@link Stream}s. */
@OnlineDocumentation
@@ -379,4 +384,46 @@ final class StreamRules {
return stream.allMatch(e -> test(e));
}
}
static final class StreamMapToIntSum<T> {
@BeforeTemplate
int before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Integer> mapper) {
return stream.map(mapper).reduce(0, Integer::sum);
}
@AfterTemplate
int after(Stream<T> stream, ToIntFunction<T> mapper) {
return stream.mapToInt(mapper).sum();
}
}
static final class StreamMapToDoubleSum<T> {
@BeforeTemplate
double before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Double> mapper) {
return stream.map(mapper).reduce(0.0, Double::sum);
}
@AfterTemplate
double after(Stream<T> stream, ToDoubleFunction<T> mapper) {
return stream.mapToDouble(mapper).sum();
}
}
static final class StreamMapToLongSum<T> {
@BeforeTemplate
long before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Long> mapper) {
return stream.map(mapper).reduce(0L, Long::sum);
}
@AfterTemplate
long after(Stream<T> stream, ToLongFunction<T> mapper) {
return stream.mapToLong(mapper).sum();
}
}
}

View File

@@ -1,6 +1,8 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Joiner;
@@ -11,21 +13,23 @@ import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link String}s. */
// XXX: Should we prefer `s -> !s.isEmpty()` or `not(String::isEmpty)`?
@OnlineDocumentation
final class StringRules {
private StringRules() {}
/** Prefer {@link String#isEmpty()} over alternatives that consult the string's length. */
// XXX: Once we target JDK 15+, generalize this rule to cover all `CharSequence` subtypes.
static final class StringIsEmpty {
@BeforeTemplate
boolean before(String str) {
@@ -39,6 +43,37 @@ final class StringRules {
}
}
/** Prefer a method reference to {@link String#isEmpty()} over the equivalent lambda function. */
// XXX: Once we target JDK 15+, generalize this rule to cover all `CharSequence` subtypes.
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
// If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.
static final class StringIsEmptyPredicate {
@BeforeTemplate
Predicate<String> before() {
return s -> s.isEmpty();
}
@AfterTemplate
Predicate<String> after() {
return String::isEmpty;
}
}
/** Prefer a method reference to {@link String#isEmpty()} over the equivalent lambda function. */
// XXX: Once we target JDK 15+, generalize this rule to cover all `CharSequence` subtypes.
static final class StringIsNotEmptyPredicate {
@BeforeTemplate
Predicate<String> before() {
return s -> !s.isEmpty();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Predicate<String> after() {
return not(String::isEmpty);
}
}
/** Prefer {@link Strings#isNullOrEmpty(String)} over the more verbose alternative. */
static final class StringIsNullOrEmpty {
@BeforeTemplate
@@ -65,7 +100,7 @@ final class StringRules {
@AfterTemplate
Optional<String> after(String str) {
return Optional.ofNullable(str).filter(s -> !s.isEmpty());
return Optional.ofNullable(str).filter(not(String::isEmpty));
}
}
@@ -76,8 +111,9 @@ final class StringRules {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Optional<String> after(Optional<String> optional) {
return optional.filter(s -> !s.isEmpty());
return optional.filter(not(String::isEmpty));
}
}

View File

@@ -0,0 +1,106 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.sun.source.tree.Tree;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link SuggestedFix}es. */
@OnlineDocumentation
final class SuggestedFixRules {
private SuggestedFixRules() {}
/** Prefer {@link SuggestedFix#delete(Tree)} over more contrived alternatives. */
static final class SuggestedFixDelete {
@BeforeTemplate
SuggestedFix before(Tree tree) {
return SuggestedFix.builder().delete(tree).build();
}
@AfterTemplate
SuggestedFix after(Tree tree) {
return SuggestedFix.delete(tree);
}
}
/** Prefer {@link SuggestedFix#replace(Tree, String)}} over more contrived alternatives. */
static final class SuggestedFixReplaceTree {
@BeforeTemplate
SuggestedFix before(Tree tree, String replaceWith) {
return SuggestedFix.builder().replace(tree, replaceWith).build();
}
@AfterTemplate
SuggestedFix after(Tree tree, String replaceWith) {
return SuggestedFix.replace(tree, replaceWith);
}
}
/** Prefer {@link SuggestedFix#replace(int, int, String)}} over more contrived alternatives. */
static final class SuggestedFixReplaceStartEnd {
@BeforeTemplate
SuggestedFix before(int start, int end, String replaceWith) {
return SuggestedFix.builder().replace(start, end, replaceWith).build();
}
@AfterTemplate
SuggestedFix after(int start, int end, String replaceWith) {
return SuggestedFix.replace(start, end, replaceWith);
}
}
/**
* Prefer {@link SuggestedFix#replace(Tree, String, int, int)}} over more contrived alternatives.
*/
static final class SuggestedFixReplaceTreeStartEnd {
@BeforeTemplate
SuggestedFix before(Tree tree, String replaceWith, int start, int end) {
return SuggestedFix.builder().replace(tree, replaceWith, start, end).build();
}
@AfterTemplate
SuggestedFix after(Tree tree, String replaceWith, int start, int end) {
return SuggestedFix.replace(tree, replaceWith, start, end);
}
}
/** Prefer {@link SuggestedFix#swap(Tree, Tree)} over more contrived alternatives. */
static final class SuggestedFixSwap {
@BeforeTemplate
SuggestedFix before(Tree tree1, Tree tree2) {
return SuggestedFix.builder().swap(tree1, tree2).build();
}
@AfterTemplate
SuggestedFix after(Tree tree1, Tree tree2) {
return SuggestedFix.swap(tree1, tree2);
}
}
/** Prefer {@link SuggestedFix#prefixWith(Tree, String)} over more contrived alternatives. */
static final class SuggestedFixPrefixWith {
@BeforeTemplate
SuggestedFix before(Tree tree, String prefix) {
return SuggestedFix.builder().prefixWith(tree, prefix).build();
}
@AfterTemplate
SuggestedFix after(Tree tree, String prefix) {
return SuggestedFix.prefixWith(tree, prefix);
}
}
/** Prefer {@link SuggestedFix#postfixWith(Tree, String)}} over more contrived alternatives. */
static final class SuggestedFixPostfixWith {
@BeforeTemplate
SuggestedFix before(Tree tree, String postfix) {
return SuggestedFix.builder().postfixWith(tree, postfix).build();
}
@AfterTemplate
SuggestedFix after(Tree tree, String postfix) {
return SuggestedFix.postfixWith(tree, postfix);
}
}
}

View File

@@ -3,21 +3,16 @@ package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AmbiguousJsonCreatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X",
containsPattern("`JsonCreator.Mode` should be set for single-argument creators"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X", containsPattern("`JsonCreator.Mode` should be set for single-argument creators"))
.addSourceLines(
"Container.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
@@ -118,7 +113,7 @@ final class AmbiguousJsonCreatorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.addInputLines(
"A.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
@@ -143,6 +138,6 @@ final class AmbiguousJsonCreatorTest {
" return FOO;",
" }",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AssertJIsNullTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass())
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
@@ -38,7 +33,7 @@ final class AssertJIsNullTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass())
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",

View File

@@ -0,0 +1,110 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AssociativeMethodInvocationTest {
@Test
void identification() {
CompilationTestHelper.newInstance(AssociativeMethodInvocation.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.matchers.Matchers;",
"import com.google.errorprone.refaster.Refaster;",
"",
"class A {",
" void m() {",
" Matchers.allOf();",
" Matchers.anyOf();",
" Refaster.anyOf();",
"",
" Matchers.allOf((t, s) -> true);",
" Matchers.anyOf((t, s) -> true);",
" Refaster.anyOf(0);",
"",
" Matchers.allOf(Matchers.anyOf((t, s) -> true));",
" Matchers.anyOf(Matchers.allOf((t, s) -> true));",
" Refaster.anyOf(Matchers.allOf((t, s) -> true));",
"",
" // BUG: Diagnostic contains:",
" Matchers.allOf(Matchers.allOf((t, s) -> true));",
" // BUG: Diagnostic contains:",
" Matchers.anyOf(Matchers.anyOf((t, s) -> true));",
" // BUG: Diagnostic contains:",
" Refaster.anyOf(Refaster.anyOf(0));",
"",
" Matchers.allOf(Matchers.allOf(ImmutableList.of((t, s) -> true)));",
" Matchers.anyOf(Matchers.anyOf(ImmutableList.of((t, s) -> true)));",
"",
" // BUG: Diagnostic contains:",
" Matchers.allOf(",
" (t, s) -> true, Matchers.allOf((t, s) -> false, (t, s) -> true), (t, s) -> false);",
" // BUG: Diagnostic contains:",
" Matchers.anyOf(",
" (t, s) -> true, Matchers.anyOf((t, s) -> false, (t, s) -> true), (t, s) -> false);",
" // BUG: Diagnostic contains:",
" Refaster.anyOf(0, Refaster.anyOf(1, 2), 3);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(AssociativeMethodInvocation.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.matchers.Matchers;",
"import com.google.errorprone.refaster.Refaster;",
"",
"class A {",
" void m() {",
" Matchers.allOf(Matchers.allOf());",
" Matchers.anyOf(Matchers.anyOf());",
" Refaster.anyOf(Refaster.anyOf());",
"",
" Matchers.allOf(Matchers.allOf((t, s) -> true));",
" Matchers.anyOf(Matchers.anyOf((t, s) -> true));",
" Refaster.anyOf(Refaster.anyOf(0));",
"",
" Matchers.allOf(",
" Matchers.anyOf(),",
" Matchers.allOf((t, s) -> false, (t, s) -> true),",
" Matchers.allOf(),",
" Matchers.anyOf((t, s) -> false));",
" Matchers.anyOf(",
" Matchers.allOf(),",
" Matchers.anyOf((t, s) -> false, (t, s) -> true),",
" Matchers.anyOf(),",
" Matchers.allOf((t, s) -> false));",
" Refaster.anyOf(Matchers.allOf(), Refaster.anyOf(1, 2), Matchers.anyOf());",
" }",
"}")
.addOutputLines(
"A.java",
"import com.google.errorprone.matchers.Matchers;",
"import com.google.errorprone.refaster.Refaster;",
"",
"class A {",
" void m() {",
" Matchers.allOf();",
" Matchers.anyOf();",
" Refaster.anyOf();",
"",
" Matchers.allOf((t, s) -> true);",
" Matchers.anyOf((t, s) -> true);",
" Refaster.anyOf(0);",
"",
" Matchers.allOf(",
" Matchers.anyOf(), (t, s) -> false, (t, s) -> true, Matchers.anyOf((t, s) -> false));",
" Matchers.anyOf(",
" Matchers.allOf(), (t, s) -> false, (t, s) -> true, Matchers.allOf((t, s) -> false));",
" Refaster.anyOf(Matchers.allOf(), 1, 2, Matchers.anyOf());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AutowiredConstructorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass())
.addSourceLines(
"Container.java",
"import com.google.errorprone.annotations.Immutable;",
@@ -71,7 +66,7 @@ final class AutowiredConstructorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass())
.addInputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CanonicalAnnotationSyntaxTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
.addSourceLines(
"pkg/A.java",
"package pkg;",
@@ -133,7 +128,7 @@ final class CanonicalAnnotationSyntaxTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
.addInputLines(
"pkg/A.java",
"package pkg;",

View File

@@ -8,14 +8,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CollectorMutabilityTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CollectorMutability.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(CollectorMutability.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
@@ -67,7 +62,7 @@ final class CollectorMutabilityTest {
@Test
void identificationWithoutGuavaOnClasspath() {
compilationTestHelper
CompilationTestHelper.newInstance(CollectorMutability.class, getClass())
.withClasspath()
.addSourceLines(
"A.java",
@@ -84,7 +79,7 @@ final class CollectorMutabilityTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass())
.addInputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
@@ -141,7 +136,7 @@ final class CollectorMutabilityTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass())
.setFixChooser(SECOND)
.addInputLines(
"A.java",

View File

@@ -0,0 +1,226 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class DirectReturnTest {
@Test
void identification() {
CompilationTestHelper.newInstance(DirectReturn.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"",
"import java.util.function.Supplier;",
"",
"class A {",
" private String field;",
"",
" void emptyMethod() {}",
"",
" void voidMethod() {",
" toString();",
" return;",
" }",
"",
" String directReturnOfParam(String param) {",
" return param;",
" }",
"",
" String assignmentToField() {",
" field = toString();",
" return field;",
" }",
"",
" Object redundantAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = toString();",
" return param;",
" }",
"",
" String redundantMockAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = mock();",
" return param;",
" }",
"",
" Object redundantMockWithExplicitTypeAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = mock(String.class);",
" return param;",
" }",
"",
" Object salientMockAssignmentToParam(String param) {",
" param = mock();",
" return param;",
" }",
"",
" String redundantAssignmentToLocalVariable() {",
" String variable = null;",
" // BUG: Diagnostic contains:",
" variable = toString();",
" return variable;",
" }",
"",
" String unusedAssignmentToLocalVariable(String param) {",
" String variable = null;",
" variable = toString();",
" return param;",
" }",
"",
" String redundantVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = toString();",
" return variable;",
" }",
"",
" String redundantSpyVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = spy();",
" return variable;",
" }",
"",
" Object redundantSpyWithExplicitTypeVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = spy(String.class);",
" return variable;",
" }",
"",
" Object salientSpyTypeVariableDeclaration() {",
" String variable = spy(\"name\");",
" return variable;",
" }",
"",
" String unusedVariableDeclaration(String param) {",
" String variable = toString();",
" return param;",
" }",
"",
" String assignmentToAnnotatedVariable() {",
" @SuppressWarnings(\"HereBeDragons\")",
" String variable = toString();",
" return variable;",
" }",
"",
" String complexReturnStatement() {",
" String variable = toString();",
" return variable + toString();",
" }",
"",
" String assignmentInsideIfClause() {",
" String variable = null;",
" if (true) {",
" variable = toString();",
" }",
" return variable;",
" }",
"",
" String redundantAssignmentInsideElseClause() {",
" String variable = toString();",
" if (true) {",
" return variable;",
" } else {",
" // BUG: Diagnostic contains:",
" variable = \"foo\";",
" return variable;",
" }",
" }",
"",
" Supplier<String> redundantAssignmentInsideLambda() {",
" return () -> {",
" // BUG: Diagnostic contains:",
" String variable = toString();",
" return variable;",
" };",
" }",
"",
" String redundantAssignmentInsideTryBlock(AutoCloseable closeable) throws Exception {",
" try (closeable) {",
" // BUG: Diagnostic contains:",
" String variable = toString();",
" return variable;",
" }",
" }",
"",
" String redundantAssignmentsInsideTryAndFinallyBlocks() {",
" String variable = toString();",
" try {",
" // BUG: Diagnostic contains:",
" variable = \"foo\";",
" return variable;",
" } finally {",
" String variable2 = toString();",
" if (true) {",
" // BUG: Diagnostic contains:",
" String variable3 = toString();",
" return variable3;",
" }",
" return variable2;",
" }",
" }",
"",
" String assignmentUsedInsideFinallyBlock() {",
" String variable = toString();",
" try {",
" variable = \"foo\";",
" return variable;",
" } finally {",
" String variable2 = toString();",
" return variable + variable2;",
" }",
" }",
"",
" String redundantAssignmentToVariableUsedInsideUnexecutedFinallyBlock(AutoCloseable closeable)",
" throws Exception {",
" String variable = toString();",
" try (closeable) {",
" if (true) {",
" // BUG: Diagnostic contains:",
" variable = \"foo\";",
" return variable;",
" }",
" }",
" try {",
" } finally {",
" return variable;",
" }",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(DirectReturn.class, getClass())
.addInputLines(
"A.java",
"class A {",
" String m1() {",
" String variable = null;",
" variable = toString();",
" return variable;",
" }",
"",
" String m2() {",
" String variable = toString();",
" return variable;",
" }",
"}")
.addOutputLines(
"A.java",
"class A {",
" String m1() {",
" String variable = null;",
" return toString();",
" }",
"",
" String m2() {",
" return toString();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class EmptyMethodTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(EmptyMethod.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(EmptyMethod.class, getClass())
.addSourceLines(
"A.java",
"class A {",
@@ -69,7 +64,7 @@ final class EmptyMethodTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass())
.addInputLines(
"A.java",
"final class A {",

View File

@@ -6,15 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ErrorProneTestHelperSourceFormatTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
ErrorProneTestHelperSourceFormat.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
@@ -63,7 +57,7 @@ final class ErrorProneTestHelperSourceFormatTest {
* Verifies that import sorting and code formatting is performed unconditionally, while unused
* imports are removed unless part of a `BugCheckerRefactoringTestHelper` expected output file.
*/
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ExplicitEnumOrderingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass())
.addSourceLines(
"A.java",
"import static java.lang.annotation.RetentionPolicy.CLASS;",

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
@@ -9,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FluxFlatMapUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FluxFlatMapUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(FluxFlatMapUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.addSourceLines(
"A.java",
"import java.util.function.BiFunction;",
@@ -73,8 +66,7 @@ final class FluxFlatMapUsageTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
BugCheckerRefactoringTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
@@ -108,7 +100,7 @@ final class FluxFlatMapUsageTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",

View File

@@ -0,0 +1,182 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.containsPattern;
import static com.google.common.base.Predicates.not;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.THIRD;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.CorePublisher;
import reactor.core.publisher.Flux;
final class FluxImplicitBlockTest {
@Test
void identification() {
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.expectErrorMessage(
"X",
and(
containsPattern("SuppressWarnings"),
containsPattern("toImmutableList"),
containsPattern("toList")))
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableList;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic matches: X",
" Flux.just(1).toIterable();",
" // BUG: Diagnostic matches: X",
" Flux.just(2).toStream();",
" // BUG: Diagnostic matches: X",
" long count = Flux.just(3).toStream().count();",
"",
" Flux.just(4).toIterable(1);",
" Flux.just(5).toIterable(2, null);",
" Flux.just(6).toStream(3);",
" new Foo().toIterable();",
" new Foo().toStream();",
" }",
"",
" class Foo<T> {",
" Iterable<T> toIterable() {",
" return ImmutableList.of();",
" }",
"",
" Stream<T> toStream() {",
" return Stream.empty();",
" }",
" }",
"}")
.doTest();
}
@Test
void identificationWithoutGuavaOnClasspath() {
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.withClasspath(CorePublisher.class, Flux.class, Publisher.class)
.expectErrorMessage("X", not(containsPattern("toImmutableList")))
.addSourceLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic matches: X",
" Flux.just(1).toIterable();",
" // BUG: Diagnostic matches: X",
" Flux.just(2).toStream();",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" }",
"}")
.addOutputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" @SuppressWarnings(\"FluxImplicitBlock\")",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.setFixChooser(SECOND)
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" Flux.just(3).toIterable().iterator();",
" Flux.just(4).toStream().count();",
" Flux.just(5) /* a */./* b */ toIterable /* c */(/* d */ ) /* e */;",
" Flux.just(6) /* a */./* b */ toStream /* c */(/* d */ ) /* e */;",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toImmutableList()).block();",
" Flux.just(2).collect(toImmutableList()).block().stream();",
" Flux.just(3).collect(toImmutableList()).block().iterator();",
" Flux.just(4).collect(toImmutableList()).block().stream().count();",
" Flux.just(5).collect(toImmutableList()).block() /* e */;",
" Flux.just(6).collect(toImmutableList()).block().stream() /* e */;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementThirdSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.setFixChooser(THIRD)
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" Flux.just(3).toIterable().iterator();",
" Flux.just(4).toStream().count();",
" Flux.just(5) /* a */./* b */ toIterable /* c */(/* d */ ) /* e */;",
" Flux.just(6) /* a */./* b */ toStream /* c */(/* d */ ) /* e */;",
" }",
"}")
.addOutputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
"",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toList()).block();",
" Flux.just(2).collect(toList()).block().stream();",
" Flux.just(3).collect(toList()).block().iterator();",
" Flux.just(4).collect(toList()).block().stream().count();",
" Flux.just(5).collect(toList()).block() /* e */;",
" Flux.just(6).collect(toList()).block().stream() /* e */;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FormatStringConcatenationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FormatStringConcatenation.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenation.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(FormatStringConcatenation.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
@@ -309,7 +304,7 @@ final class FormatStringConcatenationTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenation.class, getClass())
.addInputLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IdentityConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IdentityConversion.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(IdentityConversion.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
@@ -184,8 +179,7 @@ final class IdentityConversionTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())
.addInputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
@@ -291,7 +285,7 @@ final class IdentityConversionTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ImmutablesSortedSetComparatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.ContiguousSet;",
@@ -109,7 +104,7 @@ final class ImmutablesSortedSetComparatorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addInputLines(
"A.java",
"import com.google.common.collect.ImmutableSortedSet;",
@@ -147,7 +142,7 @@ final class ImmutablesSortedSetComparatorTest {
@Test
void replacementWithImportClash() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addInputLines(
"MySpringService.java",
"import com.google.common.collect.ImmutableSortedSet;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IsInstanceLambdaUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass())
.addSourceLines(
"A.java",
"import java.util.stream.Stream;",
@@ -46,7 +41,7 @@ final class IsInstanceLambdaUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass())
.addInputLines(
"A.java",
"import java.util.stream.Stream;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitClassModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass())
.addSourceLines(
"Container.java",
"import org.junit.jupiter.api.Test;",
@@ -98,7 +93,7 @@ final class JUnitClassModifiersTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass())
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitMethodDeclarationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(JUnitMethodDeclaration.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
@@ -345,7 +340,7 @@ final class JUnitMethodDeclarationTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclaration.class, getClass())
.addInputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",

View File

@@ -0,0 +1,496 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitValueSourceTest {
@Test
void identification() {
CompilationTestHelper.newInstance(JUnitValueSource.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import java.util.Optional;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" private static Stream<Arguments> identificationTestCases() {",
" return Stream.of(arguments(1), Arguments.of(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"identificationTestCases\")",
" void identification(int foo) {}",
"",
" private static int[] identificationWithParensTestCases() {",
" return new int[] {1, 2};",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"identificationWithParensTestCases()\")",
" void identificationWithParens(int foo) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"valueFactoryMissingTestCases\")",
" void valueFactoryMissing(int foo) {}",
"",
" private static Stream<Arguments> multipleUsagesTestCases() {",
" return Stream.of(arguments(1), Arguments.of(2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleUsagesTestCases\")",
" void multipleUsages1(int foo) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleUsagesTestCases()\")",
" void multipleUsages2(int bar) {}",
"",
" private static Stream<Arguments> valueFactoryRepeatedTestCases() {",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource({\"valueFactoryRepeatedTestCases\", \"valueFactoryRepeatedTestCases\"})",
" void valueFactoryRepeated(int foo) {}",
"",
" private static Stream<Arguments> multipleParametersTestCases() {",
" return Stream.of(arguments(1, 2), arguments(3, 4));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleParametersTestCases\")",
" void multipleParameters(int first, int second) {}",
"",
" private static int[] arrayWithoutInitializersTestCases() {",
" return new int[1];",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayWithoutInitializersTestCases\")",
" void arrayWithoutInitializers(int foo) {}",
"",
" private static Stream<Arguments> runtimeValueTestCases() {",
" int second = 2;",
" return Stream.of(arguments(1), arguments(second));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"runtimeValueTestCases\")",
" void runtimeValue(int foo) {}",
"",
" private static Stream<Arguments> streamChainTestCases() {",
" return Stream.of(1, 2).map(Arguments::arguments);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamChainTestCases\")",
" void streamChain(int number) {}",
"",
" private static Stream<Arguments> multipleReturnsTestCases() {",
" if (true) {",
" return Stream.of(arguments(1), arguments(2));",
" } else {",
" return Stream.of(arguments(3), arguments(4));",
" }",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleReturnsTestCases\")",
" void multipleReturns(int number) {}",
"",
" private static Stream<Arguments> multipleFactoriesFooTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" private static Stream<Arguments> multipleFactoriesBarTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" @ParameterizedTest",
" @MethodSource({\"multipleFactoriesFooTestCases\", \"multipleFactoriesBarTestCases\"})",
" void multipleFactories(int i) {}",
"",
" private static Stream<Arguments> extraArgsTestCases() {",
" return Stream.of(arguments(1), arguments(1, 2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"extraArgsTestCases\")",
" void extraArgs(int... i) {}",
"",
" private static Stream<Arguments> localClassTestCases() {",
" class Foo {",
" Stream<Arguments> foo() {",
" return Stream.of(arguments(1), arguments(2));",
" }",
" }",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"localClassTestCases\")",
" void localClass(int i) {}",
"",
" private static Stream<Arguments> lambdaReturnTestCases() {",
" int foo =",
" Optional.of(10)",
" .map(",
" i -> {",
" return i / 2;",
" })",
" .orElse(0);",
" return Stream.of(arguments(1), arguments(1));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"lambdaReturnTestCases\")",
" void lambdaReturn(int i) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"tech.picnic.errorprone.Foo#fooTestCases\")",
" void staticMethodReference(int foo) {}",
"",
" private static Stream<Arguments> valueFactoryWithArgumentTestCases(int amount) {",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"valueFactoryWithArgumentTestCases\")",
" void valueFactoryWithArgument(int foo) {}",
"",
" private static Arguments[] emptyArrayValueFactoryTestCases() {",
" return new Arguments[] {};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"emptyArrayValueFactoryTestCases\")",
" void emptyArrayValueFactory(int foo) {}",
"",
" private static Stream<Arguments> emptyStreamValueFactoryTestCases() {",
" return Stream.of();",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"emptyStreamValueFactoryTestCases\")",
" void emptyStreamValueFactory(int foo) {}",
"",
" private static Arguments[] invalidValueFactoryArgumentsTestCases() {",
" return new Arguments[] {arguments(1), arguments(new Object() {})};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"invalidValueFactoryArgumentsTestCases\")",
" void invalidValueFactoryArguments(int foo) {}",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(JUnitValueSource.class, getClass())
.addInputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.List;",
"import java.util.Set;",
"import java.util.stream.DoubleStream;",
"import java.util.stream.IntStream;",
"import java.util.stream.LongStream;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" private static final boolean CONST_BOOLEAN = false;",
" private static final byte CONST_BYTE = 42;",
" private static final char CONST_CHARACTER = 'a';",
" private static final short CONST_SHORT = 42;",
" private static final int CONST_INTEGER = 42;",
" private static final long CONST_LONG = 42;",
" private static final float CONST_FLOAT = 42;",
" private static final double CONST_DOUBLE = 42;",
" private static final String CONST_STRING = \"foo\";",
"",
" private static Stream<Arguments> streamOfBooleanArguments() {",
" return Stream.of(arguments(false), arguments(true), arguments(CONST_BOOLEAN));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfBooleanArguments\")",
" void primitiveBoolean(boolean b) {}",
"",
" private static Stream<Object> streamOfBooleansAndBooleanArguments() {",
" return Stream.of(false, arguments(true), CONST_BOOLEAN);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfBooleansAndBooleanArguments\")",
" void boxedBoolean(Boolean b) {}",
"",
" private static List<Arguments> listOfByteArguments() {",
" return List.of(arguments((byte) 0), arguments((byte) 1), arguments(CONST_BYTE));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"listOfByteArguments\")",
" void primitiveByte(byte b) {}",
"",
" private static List<Object> listOfBytesAndByteArguments() {",
" return List.of((byte) 0, arguments((byte) 1), CONST_BYTE);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"listOfBytesAndByteArguments\")",
" void boxedByte(Byte b) {}",
"",
" private static Set<Arguments> setOfCharacterArguments() {",
" return Set.of(arguments((char) 0), arguments((char) 1), arguments(CONST_CHARACTER));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"setOfCharacterArguments\")",
" void primitiveCharacter(char c) {}",
"",
" private static Set<Object> setOfCharactersAndCharacterArguments() {",
" return Set.of((char) 0, arguments((char) 1), CONST_CHARACTER);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"setOfCharactersAndCharacterArguments\")",
" void boxedCharacter(Character c) {}",
"",
" private static Arguments[] arrayOfShortArguments() {",
" return new Arguments[] {arguments((short) 0), arguments((short) 1), arguments(CONST_SHORT)};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayOfShortArguments\")",
" void primitiveShort(short s) {}",
"",
" private static Object[] arrayOfShortsAndShortArguments() {",
" return new Object[] {(short) 0, arguments((short) 1), CONST_SHORT};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayOfShortsAndShortArguments\")",
" void boxedShort(Short s) {}",
"",
" private static IntStream intStream() {",
" return IntStream.of(0, 1, CONST_INTEGER);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"intStream\")",
" void primitiveInteger(int i) {}",
"",
" private static int[] intArray() {",
" return new int[] {0, 1, CONST_INTEGER};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"intArray\")",
" void boxedInteger(Integer i) {}",
"",
" private static LongStream longStream() {",
" return LongStream.of(0, 1, CONST_LONG);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"longStream\")",
" void primitiveLong(long l) {}",
"",
" private static long[] longArray() {",
" return new long[] {0, 1, CONST_LONG};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"longArray\")",
" void boxedLong(Long l) {}",
"",
" private static ImmutableList<Arguments> immutableListOfFloatArguments() {",
" return ImmutableList.of(arguments(0.0F), arguments(1.0F), arguments(CONST_FLOAT));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"immutableListOfFloatArguments\")",
" void primitiveFloat(float f) {}",
"",
" private static Stream<Object> streamOfFloatsAndFloatArguments() {",
" return Stream.of(0.0F, arguments(1.0F), CONST_FLOAT);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfFloatsAndFloatArguments\")",
" void boxedFloat(Float f) {}",
"",
" private static DoubleStream doubleStream() {",
" return DoubleStream.of(0, 1, CONST_DOUBLE);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"doubleStream\")",
" void primitiveDouble(double d) {}",
"",
" private static double[] doubleArray() {",
" return new double[] {0, 1, CONST_DOUBLE};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"doubleArray\")",
" void boxedDouble(Double d) {}",
"",
" private static ImmutableSet<Arguments> immutableSetOfStringArguments() {",
" return ImmutableSet.of(arguments(\"foo\"), arguments(\"bar\"), arguments(CONST_STRING));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"immutableSetOfStringArguments\")",
" void string(String s) {}",
"",
" private static Stream<Class<?>> streamOfClasses() {",
" return Stream.of(Stream.class, java.util.Map.class);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfClasses\")",
" void clazz(Class<?> c) {}",
"",
" private static Stream<Arguments> sameNameFactoryTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" private static Stream<Arguments> sameNameFactoryTestCases(int overload) {",
" return Stream.of(arguments(overload));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"sameNameFactoryTestCases\")",
" void sameNameFactory(int i) {}",
"}")
.addOutputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.List;",
"import java.util.Set;",
"import java.util.stream.DoubleStream;",
"import java.util.stream.IntStream;",
"import java.util.stream.LongStream;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"import org.junit.jupiter.params.provider.ValueSource;",
"",
"class A {",
" private static final boolean CONST_BOOLEAN = false;",
" private static final byte CONST_BYTE = 42;",
" private static final char CONST_CHARACTER = 'a';",
" private static final short CONST_SHORT = 42;",
" private static final int CONST_INTEGER = 42;",
" private static final long CONST_LONG = 42;",
" private static final float CONST_FLOAT = 42;",
" private static final double CONST_DOUBLE = 42;",
" private static final String CONST_STRING = \"foo\";",
"",
" @ParameterizedTest",
" @ValueSource(booleans = {false, true, CONST_BOOLEAN})",
" void primitiveBoolean(boolean b) {}",
"",
" @ParameterizedTest",
" @ValueSource(booleans = {false, true, CONST_BOOLEAN})",
" void boxedBoolean(Boolean b) {}",
"",
" @ParameterizedTest",
" @ValueSource(bytes = {(byte) 0, (byte) 1, CONST_BYTE})",
" void primitiveByte(byte b) {}",
"",
" @ParameterizedTest",
" @ValueSource(bytes = {(byte) 0, (byte) 1, CONST_BYTE})",
" void boxedByte(Byte b) {}",
"",
" @ParameterizedTest",
" @ValueSource(chars = {(char) 0, (char) 1, CONST_CHARACTER})",
" void primitiveCharacter(char c) {}",
"",
" @ParameterizedTest",
" @ValueSource(chars = {(char) 0, (char) 1, CONST_CHARACTER})",
" void boxedCharacter(Character c) {}",
"",
" @ParameterizedTest",
" @ValueSource(shorts = {(short) 0, (short) 1, CONST_SHORT})",
" void primitiveShort(short s) {}",
"",
" @ParameterizedTest",
" @ValueSource(shorts = {(short) 0, (short) 1, CONST_SHORT})",
" void boxedShort(Short s) {}",
"",
" @ParameterizedTest",
" @ValueSource(ints = {0, 1, CONST_INTEGER})",
" void primitiveInteger(int i) {}",
"",
" @ParameterizedTest",
" @ValueSource(ints = {0, 1, CONST_INTEGER})",
" void boxedInteger(Integer i) {}",
"",
" @ParameterizedTest",
" @ValueSource(longs = {0, 1, CONST_LONG})",
" void primitiveLong(long l) {}",
"",
" @ParameterizedTest",
" @ValueSource(longs = {0, 1, CONST_LONG})",
" void boxedLong(Long l) {}",
"",
" @ParameterizedTest",
" @ValueSource(floats = {0.0F, 1.0F, CONST_FLOAT})",
" void primitiveFloat(float f) {}",
"",
" @ParameterizedTest",
" @ValueSource(floats = {0.0F, 1.0F, CONST_FLOAT})",
" void boxedFloat(Float f) {}",
"",
" @ParameterizedTest",
" @ValueSource(doubles = {0, 1, CONST_DOUBLE})",
" void primitiveDouble(double d) {}",
"",
" @ParameterizedTest",
" @ValueSource(doubles = {0, 1, CONST_DOUBLE})",
" void boxedDouble(Double d) {}",
"",
" @ParameterizedTest",
" @ValueSource(strings = {\"foo\", \"bar\", CONST_STRING})",
" void string(String s) {}",
"",
" @ParameterizedTest",
" @ValueSource(classes = {Stream.class, java.util.Map.class})",
" void clazz(Class<?> c) {}",
"",
" private static Stream<Arguments> sameNameFactoryTestCases(int overload) {",
" return Stream.of(arguments(overload));",
" }",
"",
" @ParameterizedTest",
" @ValueSource(ints = 1)",
" void sameNameFactory(int i) {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -7,22 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationAttributeListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:LexicographicalAnnotationAttributeListing:Includes=pkg.A.Foo,pkg.A.Bar",
"-XepOpt:LexicographicalAnnotationAttributeListing:Excludes=pkg.A.Bar#value"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.addSourceLines(
"A.java",
"import static java.math.RoundingMode.DOWN;",
@@ -165,7 +152,8 @@ final class LexicographicalAnnotationAttributeListingTest {
// `CanonicalAnnotationSyntax` checker correct the situation in a subsequent run.
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass())
.addInputLines(
"A.java",
"import static java.math.RoundingMode.DOWN;",
@@ -246,7 +234,11 @@ final class LexicographicalAnnotationAttributeListingTest {
@Test
void filtering() {
/* Some violations are not flagged because they are not in- or excluded. */
restrictedCompilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:LexicographicalAnnotationAttributeListing:Includes=pkg.A.Foo,pkg.A.Bar",
"-XepOpt:LexicographicalAnnotationAttributeListing:Excludes=pkg.A.Bar#value"))
.addSourceLines(
"pkg/A.java",
"package pkg;",

View File

@@ -6,15 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationListing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.addSourceLines(
"A.java",
"import java.lang.annotation.ElementType;",
@@ -133,7 +127,7 @@ final class LexicographicalAnnotationListingTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.addInputLines(
"A.java",
"import java.lang.annotation.ElementType;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MethodReferenceUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MethodReferenceUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MethodReferenceUsage.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.Streams;",
@@ -323,7 +318,7 @@ final class MethodReferenceUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsage.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Collections.emptyList;",

View File

@@ -6,16 +6,12 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MissingRefasterAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MissingRefasterAnnotation.class, getClass())
.expectErrorMessage(
"X",
containsPattern(
"The Refaster rule contains a method without any Refaster annotations"));
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MissingRefasterAnnotation.class, getClass())
.expectErrorMessage(
"X",
containsPattern("The Refaster rule contains a method without any Refaster annotations"))
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.annotation.AfterTemplate;",

View File

@@ -0,0 +1,136 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoMockClassReferenceTest {
@Test
void identification() {
CompilationTestHelper.newInstance(MockitoMockClassReference.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import java.util.List;",
"import java.util.Objects;",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" {",
" Double d = Objects.requireNonNullElseGet(null, () -> mock(Double.class));",
" Double d2 =",
" Objects.requireNonNullElseGet(",
" null,",
" () -> {",
" return mock(Double.class);",
" });",
" }",
"",
" void m() {",
" Number variableMock = 42;",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class);",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, \"name\");",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, InvocationOnMock::callRealMethod);",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, withSettings());",
" variableMock = mock(Integer.class);",
" variableMock = 42;",
" // BUG: Diagnostic contains:",
" List rawMock = mock(List.class);",
" // BUG: Diagnostic contains:",
" List<String> genericMock = mock(List.class);",
" var varMock = mock(Integer.class);",
" Class<? extends Number> numberType = Integer.class;",
" Number variableTypeMock = mock(numberType);",
" Object subtypeMock = mock(Integer.class);",
"",
" Number variableSpy = 42;",
" // BUG: Diagnostic contains:",
" variableSpy = spy(Number.class);",
" variableSpy = spy(Integer.class);",
" variableSpy = 42;",
" // BUG: Diagnostic contains:",
" List rawSpy = spy(List.class);",
" // BUG: Diagnostic contains:",
" List<String> genericSpy = spy(List.class);",
" var varSpy = spy(Integer.class);",
" Number variableTypeSpy = spy(numberType);",
" Object subtypeSpy = spy(Integer.class);",
" Object objectSpy = spy(new Object());",
"",
" Objects.hash(mock(Integer.class));",
" Integer i = mock(mock(Integer.class));",
" String s = new String(mock(String.class));",
" }",
"",
" Double getDoubleMock() {",
" return Objects.requireNonNullElseGet(",
" null,",
" () -> {",
" return mock(Double.class);",
" });",
" }",
"",
" Integer getIntegerMock() {",
" // BUG: Diagnostic contains:",
" return mock(Integer.class);",
" }",
"",
" <T> T getGenericMock(Class<T> clazz) {",
" return mock(clazz);",
" }",
"",
" Number getSubTypeMock() {",
" return mock(Integer.class);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(MockitoMockClassReference.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" void m() {",
" Number simpleMock = mock(Number.class);",
" Number namedMock = mock(Number.class, \"name\");",
" Number customAnswerMock = mock(Number.class, InvocationOnMock::callRealMethod);",
" Number customSettingsMock = mock(Number.class, withSettings());",
" Number simpleSpy = spy(Number.class);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" void m() {",
" Number simpleMock = mock();",
" Number namedMock = mock(\"name\");",
" Number customAnswerMock = mock(InvocationOnMock::callRealMethod);",
" Number customSettingsMock = mock(withSettings());",
" Number simpleSpy = spy();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoStubbingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MockitoStubbing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MockitoStubbing.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
@@ -55,7 +50,7 @@ final class MockitoStubbingTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbing.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class NestedOptionalsTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(NestedOptionals.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(NestedOptionals.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Optional;",
@@ -42,7 +39,7 @@ final class NestedOptionalsTest {
@Test
void identificationOptionalTypeNotLoaded() {
compilationTestHelper
CompilationTestHelper.newInstance(NestedOptionals.class, getClass())
.addSourceLines(
"A.java",
"import java.time.Duration;",

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class NonEmptyMonoTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
@@ -111,7 +106,7 @@ final class NonEmptyMonoTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass())
.addInputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",

View File

@@ -5,22 +5,21 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
// XXX: There are no tests for multiple replacements within the same expression:
// - Error Prone doesn't currently support this, it seems.
// - The `BugCheckerRefactoringTestHelper` throws an exception in this case.
// - During actual compilation only the first replacement is applied.
// XXX: Can we perhaps work-around this by describing the fixes in reverse order?
final class PrimitiveComparisonTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass());
// XXX: There are no tests for multiple replacements within the same expression:
// - Error Prone doesn't currently support this, it seems.
// - The `BugCheckerRefactoringTestHelper` throws an exception in this case.
// - During actual compilation only the first replacement is applied.
// XXX: Can we perhaps work-around this by describing the fixes in reverse order?
// The logic for `char` and `short` is exactly analogous to the `byte` case.
/**
* Tests the identification of {@code byte} comparisons for which boxing can be avoided.
*
* @implNote The logic for {@code char} and {@code short} is exactly analogous to the {@code byte}
* case.
*/
@Test
void byteComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -120,7 +119,7 @@ final class PrimitiveComparisonTest {
@Test
void intComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -227,7 +226,7 @@ final class PrimitiveComparisonTest {
@Test
void longComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -318,7 +317,7 @@ final class PrimitiveComparisonTest {
@Test
void floatComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -386,7 +385,7 @@ final class PrimitiveComparisonTest {
@Test
void doubleComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -461,7 +460,7 @@ final class PrimitiveComparisonTest {
@Test
void stringComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -497,7 +496,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariants() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -577,7 +576,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariants() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -643,7 +642,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariantsUsingStaticImports() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Comparator.comparing;",
@@ -682,7 +681,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariantsUsingStaticImports() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Comparator.comparingDouble;",
@@ -723,7 +722,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariantsInComplexSyntacticalContext() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -755,7 +754,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariantsInComplexSyntacticalContext() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",

View File

@@ -10,19 +10,9 @@ import org.junit.jupiter.api.Test;
// `String.valueOf(null)` may not. That is because the latter matches `String#valueOf(char[])`. We
// could special-case `null` arguments, but that doesn't seem worth the trouble.
final class RedundantStringConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass());
private final CompilationTestHelper customizedCompilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:RedundantStringConversion:ExtraConversionMethods=java.lang.Enum#name(),A#name(),A.B#toString(int)"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RedundantStringConversion.class, getClass());
@Test
void identificationOfIdentityTransformation() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"class A {",
@@ -45,7 +35,7 @@ final class RedundantStringConversionTest {
@Test
void identificationWithinMutatingAssignment() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import java.math.BigInteger;",
@@ -106,7 +96,7 @@ final class RedundantStringConversionTest {
@Test
void identificationWithinBinaryOperation() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import java.math.BigInteger;",
@@ -201,7 +191,7 @@ final class RedundantStringConversionTest {
@Test
void identificationWithinStringBuilderMethod() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import java.math.BigInteger;",
@@ -256,7 +246,7 @@ final class RedundantStringConversionTest {
// XXX: Also test the other formatter methods.
@Test
void identificationWithinFormatterMethod() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Formattable;",
@@ -301,7 +291,7 @@ final class RedundantStringConversionTest {
@Test
void identificationWithinGuavaGuardMethod() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
@@ -361,7 +351,7 @@ final class RedundantStringConversionTest {
@Test
void identificationWithinSlf4jLoggerMethod() {
compilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Formattable;",
@@ -416,7 +406,10 @@ final class RedundantStringConversionTest {
@Test
void identificationOfCustomConversionMethod() {
customizedCompilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:RedundantStringConversion:ExtraConversionMethods=java.lang.Enum#name(),A#name(),A.B#toString(int)"))
.addSourceLines(
"A.java",
"import java.math.RoundingMode;",
@@ -509,7 +502,7 @@ final class RedundantStringConversionTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addInputLines(
"A.java",
"class A {",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RefasterAnyOfUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RefasterAnyOfUsage.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",
@@ -38,7 +33,7 @@ final class RefasterAnyOfUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsage.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RefasterRuleModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RefasterRuleModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RefasterRuleModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RefasterRuleModifiers.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
@@ -136,7 +131,7 @@ final class RefasterRuleModifiersTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RefasterRuleModifiers.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestMappingAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestMappingAnnotation.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RequestMappingAnnotation.class, getClass())
.addSourceLines(
"A.java",
"import java.io.InputStream;",
@@ -19,6 +16,8 @@ final class RequestMappingAnnotationTest {
"import javax.servlet.http.HttpServletRequest;",
"import javax.servlet.http.HttpServletResponse;",
"import org.springframework.http.HttpMethod;",
"import org.springframework.ui.Model;",
"import org.springframework.validation.BindingResult;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PatchMapping;",
@@ -85,6 +84,12 @@ final class RequestMappingAnnotationTest {
" A properHttpMethod(HttpMethod method);",
"",
" @RequestMapping",
" A properModel(Model model);",
"",
" @RequestMapping",
" A properBindingResult(BindingResult result);",
"",
" @RequestMapping",
" A properNativeWebRequest(NativeWebRequest request);",
"",
" @RequestMapping",

View File

@@ -4,16 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestParamTypeTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.setArgs(
"-XepOpt:RequestParamType:SupportedCustomTypes=com.google.common.collect.ImmutableSet,com.google.common.collect.ImmutableSortedMultiset");
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableBiMap;",
@@ -70,7 +63,9 @@ final class RequestParamTypeTest {
@Test
void identificationRestricted() {
restrictedCompilationTestHelper
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.setArgs(
"-XepOpt:RequestParamType:SupportedCustomTypes=com.google.common.collect.ImmutableSet,com.google.common.collect.ImmutableSortedMultiset")
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableBiMap;",

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