Compare commits

...

54 Commits

Author SHA1 Message Date
Stephan Schroevers
c9a25d8173 Improve MethodReferenceUsageCheck, document further improvements 2024-02-10 15:16:05 +01:00
Picnic-Bot
17035a1623 Upgrade Spring Boot 2.7.2 -> 2.7.3 (#207)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.3
- https://github.com/spring-projects/spring-boot/compare/v2.7.2...v2.7.3
2022-08-20 09:54:57 +02:00
Picnic-Bot
e7dacd19d7 Upgrade Maven API 3.6.3 -> 3.8.6 (#184)
See:
- https://maven.apache.org/release-notes-all.html
- https://github.com/apache/maven/releases/tag/maven-3.8.4
- https://github.com/apache/maven/releases/tag/maven-3.8.5
- https://github.com/apache/maven/releases/tag/maven-3.8.6
- https://github.com/apache/maven/compare/maven-3.6.3...maven-3.8.6
2022-08-19 19:05:18 +02:00
Bastien Diederichs
e64af1dde0 Don't enforce sorting of Spring resource locations (#204)
Because their order matters.
2022-08-19 15:29:21 +02:00
Rick Ossendrijver
a58630bccf Improve StreamMapToOptionalGet Refaster template documentation (#203) 2022-08-19 13:33:23 +02:00
Gijs de Jong
9ab5bbe042 Require static import of com.google.errorprone.matchers.Matchers methods (#201) 2022-08-18 15:21:16 +02:00
Stephan Schroevers
4ca75c6cf6 Enable Error Prone's VoidMissingNullable check (#180) 2022-08-18 14:28:01 +02:00
Ferdinand Swoboda
7883b31eb6 Introduce ImmutablesSortedSetComparator check (#102) 2022-08-18 11:33:20 +02:00
Stephan Schroevers
ef751ce785 Drop various vacuous null checks (#191)
Recent versions of Error Prone guarantee that `ASTHelpers#getSymbol`
never returns `null`.
2022-08-18 11:25:35 +02:00
Vincent Koeman
130c3d0bc3 Introduce NestedOptionals check (#202) 2022-08-17 22:14:31 +02:00
Picnic-Bot
c89e3905bf Upgrade Mockito 4.6.1 -> 4.7.0 (#196)
See:
- https://github.com/mockito/mockito/releases/tag/v4.7.0
- https://github.com/mockito/mockito/compare/v4.6.1...v4.7.0
2022-08-17 16:37:54 +02:00
Picnic-Bot
21421ce753 Upgrade maven-javadoc-plugin 3.4.0 -> 3.4.1 (#195)
See:
- https://github.com/apache/maven-javadoc-plugin/releases/tag/maven-javadoc-plugin-3.4.1
- https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.4.0...maven-javadoc-plugin-3.4.1
2022-08-17 16:00:15 +02:00
Ivan Fedorov
c39d1251d2 Rewrite another ThrowableAssertAlternative#withMessage(String, Object...) expression (#190) 2022-08-17 07:38:22 +02:00
Dario Deya Diaz
9bc732b4fe Have RequestMappingAnnotation recognize @RequestAttribute parameters (#189) 2022-08-12 20:56:28 +02:00
Picnic-Bot
74100b6c41 Upgrade Project Reactor 2020.0.21 -> 2020.0.22 (#187)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.22
- https://github.com/reactor/reactor/compare/2020.0.21...2020.0.22
2022-08-11 08:21:06 +02:00
Stephan Schroevers
624f2ce753 [maven-release-plugin] prepare for next development iteration 2022-08-10 15:06:32 +02:00
Stephan Schroevers
967017eed9 [maven-release-plugin] prepare release v0.1.0 2022-08-10 15:06:30 +02:00
Rick Ossendrijver
b06945b833 Introduce utilities to validate Refaster template collections (#43)
The `Refaster` check is now located in the new `refaster-runner` Maven module.

The new `refaster-test-support` module exposes
`RefasterTemplateCollection#validate`, which for a given template collection
validates:
- That there exist corresponding `*Test{Input,Output}.java` files.
- That each template is exercised by precisely one method in these files.
2022-08-10 14:40:07 +02:00
Rick Ossendrijver
ef562c1644 Rewrite more ThrowableAssertAlternative#withMessage(String, Object...) expressions (#185) 2022-08-10 14:32:44 +02:00
Rick Ossendrijver
efbde936dc Only suggest bulk removal operations on sets (#186)
If a collection may contain duplicates, then removing a single occurrence of
each element in some other set is not equivalent to removing all such
occurrences.
2022-08-10 14:09:36 +02:00
Stephan Schroevers
c57653dd5b Introduce run-mutation-tests.sh (#170)
This script executes Pitest to determine the code base' mutation test coverage.
The set of tests executed can optionally be restricted by name. The results are 
found in each Maven module's `target/pit-reports` directory.
2022-08-09 14:38:19 +02:00
Stephan Schroevers
12b03e95b1 Manage version of org.apache.maven:maven-plugin-api (#183)
By managing the version of this dependency we expect Renovate to file a pull
request any time a new Maven version is released. Through a shared property,
this also enforces that the project is built using the latest Maven release.
2022-08-08 13:57:51 +02:00
Stephan Schroevers
c2f24ac739 Speed up the build (#169)
The build is sped up in two ways:
1. By tweaking the JVM flags passed to the main process.
2. By tweaking the JVM flags passed to the forked Surefire processes.
2022-08-08 13:41:27 +02:00
Rick Ossendrijver
4f5ea8beac Migrate from Travis CI to GitHub Actions (#158)
While there, drop the now-obsolete Takari Maven plugin.
2022-08-07 18:33:25 +02:00
Stephan Schroevers
21646ffcb1 Enable the BugPatternNaming check (#86)
While there, introduce a `.util` subpackage for non-`Bugchecker` classes.
2022-08-07 18:12:10 +02:00
Picnic-Bot
c58dceb9df Upgrade errorprone-slf4j 0.1.12 -> 0.1.13 (#53)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.13
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.12...v0.1.13
2022-08-07 14:21:02 +02:00
Picnic-Bot
90ef2f4042 Upgrade pitest-maven-plugin 1.9.3 -> 1.9.4 (#178)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.4
- https://github.com/hcoles/pitest/compare/1.9.3...1.9.4
2022-08-06 10:17:26 +02:00
Picnic-Bot
459a498d6c Upgrade maven-site-plugin 3.12.0 -> 3.12.1 (#177)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MSITE%20AND%20fixVersion%20%3E%203.12.0%20AND%20fixVersion%20%3C%3D%203.12.1%20AND%20statusCategory%20%3D%20Done%20
- https://github.com/apache/maven-site-plugin/compare/maven-site-plugin-3.12.0...maven-site-plugin-3.12.1
2022-08-06 09:39:38 +02:00
Picnic-Bot
78035644dc Upgrade NullAway 0.9.8 -> 0.9.9 (#175)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.8...v0.9.9
2022-08-05 09:10:49 +02:00
Picnic-Bot
ef67d41512 Upgrade pitest-maven-plugin 1.9.2 -> 1.9.3 (#159)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.3
- https://github.com/hcoles/pitest/compare/1.9.2...1.9.3
2022-08-04 16:53:54 +02:00
Picnic-Bot
4cecff923a Upgrade Checker Framework Annotations 3.23.0 -> 3.24.0 (#174)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.24.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.23.0...checker-framework-3.24.0
2022-08-04 08:10:10 +02:00
Stephan Schroevers
38a57db994 Introduce ErrorProneTestHelperSourceFormat check (#147)
This new checker inspects inline code passed to `CompilationTestHelper` and
`BugCheckerRefactoringTestHelper` instances. It requires that this code is
properly formatted and that its imports are organized. Only code that
represents the expected output of a refactoring operation is allowed to have
unused imports, as most `BugChecker`s do not (and are not able to) remove
imports that become obsolete as a result of applying their suggested fix(es).
2022-08-03 21:47:31 +02:00
Stephan Schroevers
336557cf8e Introduce apply-error-prone-suggestions.sh (#171)
This script compiles the code using Error Prone and applies its suggestions. 
The set of checks applied can optionally be restricted by name.
2022-08-03 17:05:38 +02:00
Picnic-Bot
9055dfff19 Upgrade Checkstyle 10.3.1 -> 10.3.2 (#173)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.2
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.1...checkstyle-10.3.2
2022-08-02 12:26:40 +02:00
Rick Ossendrijver
3712a15195 Implement workaround for MCOMPILER-503 (#149)
By moving around some annotation processor classpath entries we ensure that the
`maven-compiler-plugin` uses the appropriate version of Error Prone.

See https://issues.apache.org/jira/browse/MCOMPILER-503
2022-08-02 09:36:46 +02:00
Picnic-Bot
9d487e4a88 Upgrade New Relic Java Agent 7.8.0 -> 7.9.0 (#168)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v7.9.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.8.0...v7.9.0
2022-08-01 17:35:58 +02:00
Picnic-Bot
b2b086761c Upgrade JUnit Jupiter 5.8.2 -> 5.9.0 (#167)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.9.0
- https://github.com/junit-team/junit5/compare/r5.8.2...r5.9.0
2022-08-01 17:27:08 +02:00
Picnic-Bot
ff64247b6d Upgrade maven-resources-plugin 3.2.0 -> 3.3.0 (#166)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MRESOURCES%20AND%20fixVersion%20%3E%203.2.0%20AND%20fixVersion%20%3C%3D%203.3.0
- https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.2.0...maven-resources-plugin-3.3.0
2022-08-01 17:15:19 +02:00
Picnic-Bot
bc7443c72d Upgrade maven-install-plugin 3.0.0 -> 3.0.1 (#164)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MINSTALL%20AND%20fixVersion%20%3E%20%203.0.0%20AND%20fixVersion%20%3C%3D%203.0.1
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-3.0.0...maven-install-plugin-3.0.1
2022-08-01 16:21:54 +02:00
Picnic-Bot
abf4d68fba Upgrade extra-enforcer-rules 1.6.0 -> 1.6.1 (#165)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.6.1
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.6.0...extra-enforcer-rules-1.6.1
2022-08-01 16:20:24 +02:00
Picnic-Bot
5c5f7d849e Upgrade Guava 31.0.1-jre -> 31.1-jre (#77)
See:
- https://guava.dev/releases/31.1-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v31.1
- https://github.com/google/guava/compare/v31.0.1...v31.1
2022-08-01 14:48:56 +02:00
Picnic-Bot
65c4694936 Upgrade Error Prone 2.10.0 -> 2.14.0 (#76)
This also requires the upgrade of errorprone-slf4j 0.1.4 -> 0.1.12.

See:
- https://github.com/google/error-prone/releases/tag/v2.11.0
- https://github.com/google/error-prone/releases/tag/v2.12.0
- https://github.com/google/error-prone/releases/tag/v2.12.1
- https://github.com/google/error-prone/releases/tag/v2.13.0
- https://github.com/google/error-prone/releases/tag/v2.13.1
- https://github.com/google/error-prone/releases/tag/v2.14.0
- https://github.com/google/error-prone/compare/v2.10.0...v2.14.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.10.0-picnic-3...v2.14.0-picnic-2
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.5
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.6
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.7
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.8
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.9
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.10
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.11
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.12
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.4...v0.1.12
2022-07-31 15:50:16 +02:00
Picnic-Bot
a45291c7d8 Upgrade Spring Boot 2.7.1 -> 2.7.2 (#163)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.2
- https://github.com/spring-projects/spring-boot/compare/v2.7.1...v2.7.2
2022-07-30 15:11:37 +02:00
Picnic-Bot
71012f31ab Upgrade swagger-annotations 2.2.1 -> 2.2.2 (#162)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.2
- https://github.com/swagger-api/swagger-core/compare/v2.2.1...v2.2.2
2022-07-30 14:43:54 +02:00
Picnic-Bot
6e0905c033 Upgrade maven-install-plugin 2.5.2 -> 3.0.0 (#161)
See: 
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MINSTALL%20AND%20fixVersion%20%3E%20%202.5.2%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-2.5.2...maven-install-plugin-3.0.0
2022-07-21 09:01:43 +02:00
Picnic-Bot
af5ac85428 Upgrade maven-deploy-plugin 2.8.2 -> 3.0.0 (#160)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEPLOY%20AND%20fixVersion%20%3E%202.8.2%20AND%20fixVersion%20%3C%3D%203.0.0
- https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-2.8.2...maven-deploy-plugin-3.0.0
2022-07-21 08:49:42 +02:00
Picnic-Bot
0329c25f78 Upgrade sortpom-maven-plugin 3.1.3 -> 3.2.0 (#157)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.2.0
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.1.3...sortpom-parent-3.2.0
2022-07-21 08:04:22 +02:00
Picnic-Bot
9e67e2b795 Upgrade Spring 5.3.21 -> 5.3.22 (#155)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.22
- https://github.com/spring-projects/spring-framework/compare/v5.3.21...v5.3.22
2022-07-15 14:52:27 +02:00
Picnic-Bot
4bafea05f4 Upgrade Project Reactor 2020.0.20 -> 2020.0.21 (#154)
See:
- https://github.com/reactor/reactor/releases/tag/2020.0.21
- https://github.com/reactor/reactor/compare/2020.0.20...2020.0.21
2022-07-15 08:46:21 +02:00
Picnic-Bot
8ce9cab2dd Upgrade Checker Framework Annotations 3.22.2 -> 3.23.0 (#153)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.23.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.22.2...checker-framework-3.23.0
2022-07-13 07:34:25 +02:00
Pieter Dirk Soels
ae30625524 Clarify MapToNullable Refaster template (#150) 2022-07-12 12:54:22 +02:00
Picnic-Bot
dc0046ebfc Upgrade extra-enforcer-rules 1.5.1 -> 1.6.0 (#152)
See:
- https://github.com/mojohaus/extra-enforcer-rules/releases/tag/extra-enforcer-rules-1.6.0
- https://github.com/mojohaus/extra-enforcer-rules/compare/extra-enforcer-rules-1.5.1...extra-enforcer-rules-1.6.0
2022-07-12 11:41:23 +02:00
Picnic-Bot
ad6d774818 Upgrade pitest-maven-plugin 1.9.0 -> 1.9.2 (#151)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.1
- https://github.com/hcoles/pitest/releases/tag/1.9.2
- https://github.com/hcoles/pitest/compare/1.9.0...1.9.2
2022-07-12 11:11:30 +02:00
Stephan Schroevers
405f5874ac Apply Checkstyle to Refaster test resources (#146)
Similar to formatting the `*TemplatesTest{In,Out}put.java` files to enforce
certain style aspects, this is expected to reduce future code review effort.

The test code changes were applied to appease the
`SimplifyBooleanExpression` check.
2022-07-06 13:39:59 +02:00
220 changed files with 5116 additions and 1990 deletions

37
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Build and verify
on:
pull_request:
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
jdk: [ 11.0.16, 17.0.4 ]
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.
- name: Check out code
uses: actions/checkout@v3.0.2
- name: Set up JDK
uses: actions/setup-java@v3.4.1
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
cache: maven
- name: Display build environment details
run: mvn --version
- name: Build project against vanilla Error Prone
run: mvn -T1C install
- name: Build project with self-check against Error Prone fork
run: mvn -T1C clean verify -Perror-prone-fork -Pnon-maven-central -Pself-check -s settings.xml
- name: Remove installed project artifacts
run: mvn build-helper:remove-project-artifact
# XXX: Enable Codecov once we "go public".
# XXX: Enable SonarCloud once we "go public".

View File

@@ -1,3 +1,6 @@
-XX:ReservedCodeCacheSize=512m
-XX:SoftRefLRUPolicyMSPerMB=10
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED

1
.mvn/maven.config Normal file
View File

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

View File

@@ -1,38 +0,0 @@
---
dist: bionic
language: java
jdk: openjdk11
addons:
sonarcloud:
organization: picnic-technologies
token: "${SONARCLOUD_TOKEN}"
install:
- mvn io.takari:maven:wrapper
script:
# We run the build twice: 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.
- ./mvnw clean install
- ./mvnw clean install -Perror-prone-fork -Pnon-maven-central -Pself-check -s settings.xml
# XXX: Enable SonarCloud once we "go public".
# ./mvnw jacoco:prepare-agent surefire:test jacoco:report sonar:sonar
- ./mvnw jacoco:prepare-agent surefire:test jacoco:report
before_cache:
# Don't cache the artifacts we just generated, for multiple reasons: (1) we
# shouldn't need them next time around and (2) if we do, that indicates a
# dependency issue which might otherwise go unnoticed until next time we bump
# the project's version (i.e., when tagging).
- find "${HOME}/.m2/repository" -depth -name '*-SNAPSHOT' -exec rm -r '{}' \;
cache:
directories:
# The local Maven repository in which third party dependencies are stored.
- ${HOME}/.m2/repository
# The Takari Maven Wrapper's storage for downloaded Maven distributions.
- ${HOME}/.m2/wrapper
# The SonarQube analysis cache.
- ${HOME}/.sonar/cache
# XXX: Enable Codecov once we "go public".
#after_success:
# - bash <(curl -s https://codecov.io/bash)

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Compiles the code using Error Prone and applies its suggestions. The set of
# checks applied can optionally be restricted by name.
#
# As this script may modify the project's code, it is important to execute it
# in a clean Git working directory.
set -e -u -o pipefail
if [ "${#}" -gt 1 ]; then
echo "Usage: ./$(basename "${0}") [PatchChecks]"
exit 1
fi
patchChecks=${1:-}
mvn clean test-compile fmt:format \
-T 1.0C \
-Perror-prone \
-Perror-prone-fork \
-Ppatch \
-Pself-check \
-Derror-prone.patch-checks="${patchChecks}" \
-Dverification.skip

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.1.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -41,15 +41,13 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</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. -->
<artifactId>refaster-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -76,6 +74,10 @@
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@@ -141,6 +143,11 @@
<artifactId>assertj-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@@ -201,15 +208,6 @@
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.spotify.fmt</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<configuration>
<additionalSourceDirectories>
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
</additionalSourceDirectories>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

View File

@@ -25,12 +25,11 @@ import javax.lang.model.element.AnnotationValue;
/** A {@link BugChecker} which flags ambiguous {@code @JsonCreator}s in enums. */
@AutoService(BugChecker.class)
@BugPattern(
name = "AmbiguousJsonCreator",
summary = "`JsonCreator.Mode` should be set for single-argument creators",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class AmbiguousJsonCreatorCheck extends BugChecker implements AnnotationTreeMatcher {
public final class AmbiguousJsonCreator extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
isType("com.fasterxml.jackson.annotation.JsonCreator");

View File

@@ -30,12 +30,11 @@ import com.sun.source.tree.MethodInvocationTree;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "AssertJIsNull",
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AssertJIsNullCheck extends BugChecker implements MethodInvocationTreeMatcher {
public final class AssertJIsNull extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
allOf(

View File

@@ -8,6 +8,7 @@ import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
@@ -26,12 +27,11 @@ import java.util.List;
/** A {@link BugChecker} which flags redundant {@code @Autowired} constructor annotations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "AutowiredConstructor",
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AutowiredConstructorCheck extends BugChecker implements ClassTreeMatcher {
public final class AutowiredConstructor extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
@@ -43,7 +43,7 @@ public final class AutowiredConstructorCheck extends BugChecker implements Class
return Description.NO_MATCH;
}
List<AnnotationTree> annotations =
ImmutableList<AnnotationTree> annotations =
AUTOWIRED_ANNOTATION
.multiMatchResult(Iterables.getOnlyElement(constructors), state)
.matchingNodes();

View File

@@ -25,25 +25,24 @@ import java.util.Optional;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags annotations that could be written more concisely. */
@AutoService(BugChecker.class)
@BugPattern(
name = "CanonicalAnnotationSyntax",
summary = "Omit redundant syntax from annotation declarations",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class CanonicalAnnotationSyntaxCheck extends BugChecker
implements AnnotationTreeMatcher {
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
FIX_FACTORIES =
ImmutableSet.of(
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
CanonicalAnnotationSyntax::dropRedundantParentheses,
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
CanonicalAnnotationSyntax::dropRedundantCurlies);
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
@@ -103,7 +102,8 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
return Optional.of(
SuggestedFix.replace(
arg,
simplifyAttributeValue(expr, state).orElseGet(() -> Util.treeToString(expr, state))));
simplifyAttributeValue(expr, state)
.orElseGet(() -> SourceCode.treeToString(expr, state))));
}
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
@@ -138,11 +138,11 @@ public final class CanonicalAnnotationSyntaxCheck extends BugChecker
private static Optional<String> simplifySingletonArray(NewArrayTree array, VisitorState state) {
return Optional.of(array.getInitializers())
.filter(initializers -> initializers.size() == 1)
.map(initializers -> Util.treeToString(initializers.get(0), state));
.map(initializers -> SourceCode.treeToString(initializers.get(0), state));
}
private static Optional<String> dropTrailingComma(NewArrayTree array, VisitorState state) {
String src = Util.treeToString(array, state);
String src = SourceCode.treeToString(array, state);
return Optional.of(TRAILING_ARRAY_COMMA.matcher(src))
.filter(Matcher::find)
.map(m -> src.substring(0, m.start()) + '}');

View File

@@ -27,14 +27,12 @@ import java.util.stream.Collector;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "CollectorMutability",
summary =
"Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class CollectorMutabilityCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
staticMethod().onClass("java.util.stream.Collectors");

View File

@@ -20,18 +20,16 @@ import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import java.util.Optional;
/** A {@link BugChecker} which flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@BugPattern(
name = "EmptyMethod",
summary = "Empty method can likely be deleted",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatcher {
public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<Tree> PERMITTED_ANNOTATION =
annotations(
@@ -48,8 +46,7 @@ public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatc
return Description.NO_MATCH;
}
MethodSymbol sym = ASTHelpers.getSymbol(tree);
if (sym == null || ASTHelpers.methodCanBeOverridden(sym)) {
if (ASTHelpers.methodCanBeOverridden(ASTHelpers.getSymbol(tree))) {
return Description.NO_MATCH;
}

View File

@@ -0,0 +1,160 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static java.util.stream.Collectors.joining;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import com.google.googlejavaformat.java.ImportOrderer;
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
import com.google.googlejavaformat.java.RemoveUnusedImports;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.Position;
import java.util.List;
import java.util.Optional;
/**
* A {@link BugChecker} which flags improperly formatted Error Prone test code.
*
* <p>All test code should be formatted in accordance with Google Java Format's {@link Formatter}
* output, and imports should be ordered according to the {@link Style#GOOGLE Google} style.
*
* <p>This checker inspects inline code passed to {@code
* com.google.errorprone.CompilationTestHelper} and {@code
* com.google.errorprone.BugCheckerRefactoringTestHelper}. It requires that this code is properly
* formatted and that its imports are organized. Only code that represents the expected output of a
* refactoring operation is allowed to have unused imports, as most {@link BugChecker}s do not (and
* are not able to) remove imports that become obsolete as a result of applying their suggested
* fix(es).
*/
// XXX: Once we target JDK 17 (optionally?) suggest text block fixes.
// XXX: GJF guesses the line separator to be used by inspecting the source. When using text blocks
// this may cause the current unconditional use of `\n` not to be sufficient when building on
// Windows; TBD.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Test code should follow the Google Java style",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class ErrorProneTestHelperSourceFormat extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Formatter FORMATTER = new Formatter();
private static final Matcher<ExpressionTree> INPUT_SOURCE_ACCEPTING_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines"),
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines"));
private static final Matcher<ExpressionTree> OUTPUT_SOURCE_ACCEPTING_METHOD =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);
if (!isOutputSource && !INPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
if (sourceLines.isEmpty()) {
return buildDescription(tree).setMessage("No source code provided").build();
}
int startPos = ASTHelpers.getStartPosition(sourceLines.get(0));
int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1));
/* Attempt to format the source code only if it fully consists of constant expressions. */
return getConstantSourceCode(sourceLines)
.map(source -> flagFormattingIssues(startPos, endPos, source, isOutputSource, state))
.orElse(Description.NO_MATCH);
}
private Description flagFormattingIssues(
int startPos, int endPos, String source, boolean retainUnusedImports, VisitorState state) {
Tree methodInvocation = state.getPath().getLeaf();
String formatted;
try {
formatted = formatSourceCode(source, retainUnusedImports).trim();
} catch (FormatterException e) {
return buildDescription(methodInvocation)
.setMessage(String.format("Source code is malformed: %s", e.getMessage()))
.build();
}
if (source.trim().equals(formatted)) {
return Description.NO_MATCH;
}
if (startPos == Position.NOPOS || endPos == Position.NOPOS) {
/*
* We have insufficient source information to emit a fix, so we only flag the fact that the
* code isn't properly formatted.
*/
return describeMatch(methodInvocation);
}
/*
* The code isn't properly formatted; replace all lines with the properly formatted
* alternatives.
*/
return describeMatch(
methodInvocation,
SuggestedFix.replace(
startPos,
endPos,
Splitter.on('\n')
.splitToStream(formatted)
.map(state::getConstantExpression)
.collect(joining(", "))));
}
private static String formatSourceCode(String source, boolean retainUnusedImports)
throws FormatterException {
String withReorderedImports = ImportOrderer.reorderImports(source, Style.GOOGLE);
String withOptionallyRemovedImports =
retainUnusedImports
? withReorderedImports
: RemoveUnusedImports.removeUnusedImports(withReorderedImports);
return FORMATTER.formatSource(withOptionallyRemovedImports);
}
private static Optional<String> getConstantSourceCode(
List<? extends ExpressionTree> sourceLines) {
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return Optional.empty();
}
source.append(value).append('\n');
}
return Optional.of(source.toString());
}
}

View File

@@ -35,13 +35,11 @@ import java.util.stream.Stream;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "ExplicitEnumOrdering",
summary = "Make sure `Ordering#explicit` lists all of an enum's values",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class ExplicitEnumOrderingCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
staticMethod().onClass(Ordering.class.getName()).named("explicit");
@@ -73,7 +71,7 @@ public final class ExplicitEnumOrderingCheck extends BugChecker
.collect(
collectingAndThen(
toImmutableSetMultimap(Symbol::asType, Symbol::toString),
ExplicitEnumOrderingCheck::getMissingEnumValues));
ExplicitEnumOrdering::getMissingEnumValues));
}
private static ImmutableSet<String> getMissingEnumValues(

View File

@@ -40,14 +40,13 @@ import reactor.core.publisher.Flux;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "FluxFlatMapUsage",
summary =
"`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; "
+ "please use `Flux#concatMap` or explicitly specify the desired amount of concurrency",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class FluxFlatMapUsageCheck extends BugChecker
public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";

View File

@@ -31,6 +31,8 @@ import com.sun.source.util.SimpleTreeVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags string concatenations that produce a format string; in such
@@ -48,12 +50,11 @@ import java.util.Optional;
// invocations, as necessary.
@AutoService(BugChecker.class)
@BugPattern(
name = "FormatStringConcatenation",
summary = "Defer string concatenation to the invoked method",
linkType = NONE,
severity = WARNING,
tags = SIMPLIFICATION)
public final class FormatStringConcatenationCheck extends BugChecker
public final class FormatStringConcatenation extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
/**
@@ -210,6 +211,7 @@ public final class FormatStringConcatenationCheck extends BugChecker
this.formatSpecifier = formatSpecifier;
}
@Nullable
@Override
public Void visitBinary(BinaryTree tree, VisitorState state) {
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
@@ -222,11 +224,13 @@ public final class FormatStringConcatenationCheck extends BugChecker
return null;
}
@Nullable
@Override
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
return tree.getExpression().accept(this, state);
}
@Nullable
@Override
protected Void defaultAction(Tree tree, VisitorState state) {
appendExpression(tree);
@@ -246,7 +250,7 @@ public final class FormatStringConcatenationCheck extends BugChecker
return state.getConstantExpression(formatString.toString())
+ ", "
+ formatArguments.stream()
.map(tree -> Util.treeToString(tree, state))
.map(tree -> SourceCode.treeToString(tree, state))
.collect(joining(", "));
}
}

View File

@@ -27,6 +27,7 @@ import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags redundant identity conversions. */
// XXX: Consider detecting cases where a flagged expression is passed to a method, and where removal
@@ -34,13 +35,11 @@ import java.util.List;
// the target method such a modification may change the code's semantics or performance.
@AutoService(BugChecker.class)
@BugPattern(
name = "IdentityConversion",
summary = "Avoid or clarify identity conversions",
linkType = NONE,
severity = WARNING,
tags = SIMPLIFICATION)
public final class IdentityConversionCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class IdentityConversion extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
@@ -95,7 +94,7 @@ public final class IdentityConversionCheck extends BugChecker
.setMessage(
"This method invocation appears redundant; remove it or suppress this warning and "
+ "add a comment explaining its purpose")
.addFix(SuggestedFix.replace(tree, Util.treeToString(sourceTree, state)))
.addFix(SuggestedFix.replace(tree, SourceCode.treeToString(sourceTree, state)))
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
.build();
}

View File

@@ -0,0 +1,81 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.methodReturns;
import static com.google.errorprone.matchers.Matchers.not;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodTree;
import java.util.SortedSet;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} which flags {@link SortedSet} property declarations inside
* {@code @Value.Immutable}- and {@code @Value.Modifiable}-annotated types that lack a
* {@code @Value.NaturalOrder} or {@code @Value.ReverseOrder} annotation.
*
* <p>Without such an annotation:
*
* <ul>
* <li>deserialization of the enclosing type requires that the associated JSON property is
* present, contrary to the way in which Immutables handles other collection properties; and
* <li>different instances may use different comparator implementations (e.g. deserialization
* would default to natural order sorting), potentially leading to subtle bugs.
* </ul>
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be "
+ "annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ImmutablesSortedSetComparator extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodTree> METHOD_LACKS_ANNOTATION =
allOf(
methodReturns(isSubtypeOf(SortedSet.class)),
anyOf(
allOf(
hasModifier(Modifier.ABSTRACT),
enclosingClass(
anyOf(
hasAnnotation("org.immutables.value.Value.Immutable"),
hasAnnotation("org.immutables.value.Value.Modifiable")))),
hasAnnotation("org.immutables.value.Value.Default")),
not(
anyOf(
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder builder = SuggestedFix.builder();
String valueTypeIdentifier =
SuggestedFixes.qualifyType(state, builder, "org.immutables.value.Value");
return describeMatch(
tree,
builder.prefixWith(tree, String.format("@%s.NaturalOrder ", valueTypeIdentifier)).build());
}
}

View File

@@ -11,7 +11,7 @@ import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.JavaKeywords.isReservedKeyword;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
@@ -36,6 +36,7 @@ import com.sun.tools.javac.code.Symbol;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check which enforces that test classes:
@@ -45,12 +46,11 @@ import javax.lang.model.element.Name;
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
@AutoService(BugChecker.class)
@BugPattern(
name = "JUnitMethodDeclaration",
summary = "JUnit method declaration can likely be improved",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class JUnitMethodDeclarationCheck extends BugChecker implements MethodTreeMatcher {
public final class JUnitMethodDeclaration extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TEST_PREFIX = "test";
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
@@ -171,13 +171,12 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = Util.treeToString(tree, state);
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(sym -> sym.getQualifiedName().toString())
return Optional.of(ASTHelpers.getSymbol(tree).getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))
.filter(not(String::isEmpty))

View File

@@ -37,6 +37,9 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags annotation array listings which aren't sorted lexicographically.
@@ -46,12 +49,11 @@ import java.util.stream.Stream;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "LexicographicalAnnotationAttributeListing",
summary = "Where possible, sort annotation array attributes lexicographically",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationAttributeListingCheck extends BugChecker
public final class LexicographicalAnnotationAttributeListing extends BugChecker
implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableSet<String> BLACKLISTED_ANNOTATIONS =
@@ -60,24 +62,27 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
"com.fasterxml.jackson.annotation.JsonPropertyOrder#value",
"io.swagger.annotations.ApiImplicitParams#value",
"io.swagger.v3.oas.annotations.Parameters#value",
"javax.xml.bind.annotation.XmlType#propOrder");
"javax.xml.bind.annotation.XmlType#propOrder",
"org.springframework.context.annotation.PropertySource#value",
"org.springframework.test.context.TestPropertySource#locations",
"org.springframework.test.context.TestPropertySource#value");
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListing:";
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
private final AnnotationAttributeMatcher matcher;
/** Instantiates the default {@link LexicographicalAnnotationAttributeListingCheck}. */
public LexicographicalAnnotationAttributeListingCheck() {
/** Instantiates the default {@link LexicographicalAnnotationAttributeListing}. */
public LexicographicalAnnotationAttributeListing() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link LexicographicalAnnotationAttributeListingCheck}.
* Instantiates a customized {@link LexicographicalAnnotationAttributeListing}.
*
* @param flags Any provided command line flags.
*/
public LexicographicalAnnotationAttributeListingCheck(ErrorProneFlags flags) {
public LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
matcher = createAnnotationAttributeMatcher(flags);
}
@@ -128,7 +133,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
/* The elements aren't sorted. Suggest the sorted alternative. */
String suggestion =
desiredOrdering.stream()
.map(expr -> Util.treeToString(expr, state))
.map(expr -> SourceCode.treeToString(expr, state))
.collect(joining(", ", "{", "}"));
return Optional.of(SuggestedFix.builder().replace(array, suggestion));
}
@@ -171,20 +176,23 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
new TreeScanner<Void, Void>() {
@Nullable
@Override
public Void visitIdentifier(IdentifierTree node, Void ctx) {
public Void visitIdentifier(IdentifierTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitIdentifier(node, ctx);
}
@Nullable
@Override
public Void visitLiteral(LiteralTree node, Void ctx) {
public Void visitLiteral(LiteralTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitLiteral(node, ctx);
}
@Nullable
@Override
public Void visitPrimitiveType(PrimitiveTypeTree node, Void ctx) {
public Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void ctx) {
nodes.add(tokenize(node));
return super.visitPrimitiveType(node, ctx);
}
@@ -194,7 +202,7 @@ public final class LexicographicalAnnotationAttributeListingCheck extends BugChe
* Tokens are split on `=` so that e.g. inline Spring property declarations are properly
* sorted by key, then value.
*/
return ImmutableList.copyOf(Util.treeToString(node, state).split("=", -1));
return ImmutableList.copyOf(SourceCode.treeToString(node, state).split("=", -1));
}
}.scan(array, null);

View File

@@ -20,6 +20,7 @@ import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags annotations that are not lexicographically sorted.
@@ -29,12 +30,11 @@ import java.util.Optional;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "LexicographicalAnnotationListing",
summary = "Sort annotations lexicographically where possible",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationListingCheck extends BugChecker
public final class LexicographicalAnnotationListing extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@@ -52,7 +52,7 @@ public final class LexicographicalAnnotationListingCheck extends BugChecker
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
Description.Builder description = buildDescription(tree);
Description.Builder description = buildDescription(originalOrdering.get(0));
fix.ifPresent(description::addFix);
return description.build();
}
@@ -60,7 +60,7 @@ public final class LexicographicalAnnotationListingCheck extends BugChecker
private static ImmutableList<? extends AnnotationTree> sort(
List<? extends AnnotationTree> annotations, VisitorState state) {
return annotations.stream()
.sorted(comparing(annotation -> Util.treeToString(annotation, state)))
.sorted(comparing(annotation -> SourceCode.treeToString(annotation, state)))
.collect(toImmutableList());
}
@@ -72,7 +72,8 @@ public final class LexicographicalAnnotationListingCheck extends BugChecker
originalAnnotations.stream(),
sortedAnnotations.stream(),
(original, replacement) ->
SuggestedFix.builder().replace(original, Util.treeToString(replacement, state)))
SuggestedFix.builder()
.replace(original, SourceCode.treeToString(replacement, state)))
.reduce(SuggestedFix.Builder::merge)
.map(SuggestedFix.Builder::build);
}

View File

@@ -0,0 +1,393 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static java.util.function.Predicate.not;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.util.ASTHelpers;
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.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} which flags lambda expressions that can be replaced with method references.
*/
// XXX: Other custom expressions we could rewrite:
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
// - `(a, b) -> a + b` to `String::concat` or `{Integer,Long,Float,Double}::sum`. Also requires null
// checking.
// - `i -> new int[i]` to `int[]::new`.
// - `() -> new Foo()` to `Foo::new` (and variations).
// XXX: Link to Effective Java, Third Edition, Item 43. In there the suggested approach is not so
// black-and-white. Maybe we can more closely approximate it?
// XXX: With Java 9's introduction of `Predicate.not`, we could write many lambda expressions to
// `not(some::reference)`.
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
// XXX: Does this check also do `() -> something.someConst` -> `something::someConst`?
// XXX: Don't rewrite `() -> field.m()` to `field::m` for non-final fields.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer method references over lambda expressions",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<Tree> REFASTER_TEMPLATE_METHOD =
anyOf(
hasAnnotation(AfterTemplate.class.getName()),
hasAnnotation(BeforeTemplate.class.getName()));
private static final String VERIFY_SUGGESTIONS_FLAG = "MethodReferenceUsage:VerifySuggestions";
/**
* Tells whether this class is loaded in inside a {@link SuggestedFixes#compilesWithFix(Fix,
* VisitorState, ImmutableList, boolean)} invocation. This allows {@link
* #tryCompileWithFix(VisitorState, SuggestedFix)} to prevent a very expensive chain of nested
* compilations.
*/
// XXX: Review whether, given the most recent improvements, we need the `compilesWithFix` fallback
// at all. (Likely at least one open point remains: handling of generic types.)
private static final boolean IS_NESTED_INVOCATION =
StackWalker.getInstance()
.walk(
frames -> {
String className = MethodReferenceUsage.class.getCanonicalName();
return frames.filter(f -> f.getClassName().equals(className)).limit(2).count() > 1;
});
private final boolean verifySuggestions;
/** Instantiates the default {@link MethodReferenceUsage}. */
public MethodReferenceUsage() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link MethodReferenceUsage}.
*
* @param flags Any provided command line flags.
*/
public MethodReferenceUsage(ErrorProneFlags flags) {
verifySuggestions = flags.getBoolean(VERIFY_SUGGESTIONS_FLAG).orElse(Boolean.FALSE);
}
@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
MethodTree enclosingMethod = state.findEnclosing(MethodTree.class);
if (enclosingMethod != null && REFASTER_TEMPLATE_METHOD.matches(enclosingMethod, state)) {
/*
* Within Refaster template methods variable references may stand in for complex expressions.
* Additionally, `@Placeholder` methods cannot be used as method handles. For both these
* reasons it is not safe to replace lambda expressions with method references inside Refaster
* templates.
*/
// XXX: This is too strict; we should more explicitly handle `@Placeholder` and method
// parameters. For example, `() -> Optional.empty()` _should_ be flagged.
return Description.NO_MATCH;
}
if (isPassedToSameArgCountMethodOverload(state)) {
// XXX: When a lambda expression is passed to an overloaded method, replacing it with a method
// reference may introduce an ambiguity about the method that is intended to be invoked. An
// example is a pair of overloads accepting a `Runnable` and `Supplier<T>` respectively, where
// the lambda expression in question returns a value: in this case the first overload is
// selected, but when converted to a method reference the intended target is no longer clear.
// Right now any lambda expression passed to an method with an overload accepting the same
// number of arguments is ignored. Improve this detection logic.
return Description.NO_MATCH;
}
/*
* Lambda expressions can be used in several places where method references cannot, either
* because the latter are not syntactically valid or ambiguous. Rather than encoding all these
* edge cases we try to compile the code with the suggested fix, to see whether this works.
*/
// XXX: Update the comment to reflect actual `tryCompileWithFix` usage.
return constructMethodRef(tree, state, tree.getBody())
.map(SuggestedFix.Builder::build)
.filter(fix -> !verifySuggestions || tryCompileWithFix(state, fix))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static boolean isPassedToSameArgCountMethodOverload(VisitorState state) {
TreePath parent = state.getPath().getParentPath();
if (parent == null) {
return false;
}
Symbol symbol = ASTHelpers.getSymbol(parent.getLeaf());
if (!(symbol instanceof MethodSymbol)) {
return false;
}
MethodSymbol method = (MethodSymbol) symbol;
return getOverloads(method).anyMatch(m -> m.params().size() == method.params.size());
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, VisitorState state, Tree subTree) {
switch (subTree.getKind()) {
case BLOCK:
return constructMethodRef(lambdaExpr, state, (BlockTree) subTree);
case EXPRESSION_STATEMENT:
return constructMethodRef(
lambdaExpr, state, ((ExpressionStatementTree) subTree).getExpression());
case METHOD_INVOCATION:
return constructMethodRef(lambdaExpr, state, (MethodInvocationTree) subTree);
case PARENTHESIZED:
// XXX: Add test!
return constructMethodRef(lambdaExpr, state, ((ParenthesizedTree) subTree).getExpression());
case RETURN:
// XXX: This case isn't tested. Reachable?
// XXX: Should be possible with `{ return x; }`.
return constructMethodRef(lambdaExpr, state, ((ReturnTree) subTree).getExpression());
default:
// XXX: Explicitly handle known cases and throw an exception otherwise?
return Optional.empty();
}
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, VisitorState state, BlockTree subTree) {
// XXX: Add test with >1 statement.
return Optional.of(subTree.getStatements())
.filter(statements -> statements.size() == 1)
.flatMap(statements -> constructMethodRef(lambdaExpr, state, statements.get(0)));
}
// XXX: Replace nested `Optional` usage.
@SuppressWarnings("NestedOptionals")
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, VisitorState state, MethodInvocationTree subTree) {
return matchParameters(lambdaExpr, subTree)
.flatMap(
expectedInstance -> constructMethodRef(lambdaExpr, state, subTree, expectedInstance));
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr,
VisitorState state,
MethodInvocationTree subTree,
Optional<Name> expectedInstance) {
ExpressionTree methodSelect = subTree.getMethodSelect();
switch (methodSelect.getKind()) {
case IDENTIFIER:
if (expectedInstance.isPresent()) {
/* Direct method call; there is no matching "implicit parameter". */
return Optional.empty();
}
// XXX: Here too test for ambiguous method references.
Symbol sym = ASTHelpers.getSymbol(methodSelect);
if (!sym.isStatic()) {
return constructFix(lambdaExpr, "this", methodSelect);
}
return constructFix(lambdaExpr, sym.owner, methodSelect);
case MEMBER_SELECT:
return constructMethodRef(
lambdaExpr, state, (MemberSelectTree) methodSelect, expectedInstance);
default:
throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind());
}
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr,
VisitorState state,
MemberSelectTree subTree,
Optional<Name> expectedInstance) {
if (subTree.getExpression().getKind() != Kind.IDENTIFIER) {
// XXX: Could be parenthesized. Handle. Also in other classes. Maybe consult
// `SideEffectAnalysis`?
// XXX: This branch isn't tested. Fix. Maybe something like `foo.bar().baz()`.
/*
* Only suggest a replacement if the method select's expression provably doesn't have
* side-effects. Otherwise the replacement may not be behavior preserving.
*/
// XXX: So do this ^.
return Optional.empty();
}
// XXX: Check whether this cast is safe in all cases.
MethodSymbol method = (MethodSymbol) ASTHelpers.getSymbol(subTree);
Symbol lhsSymbol = ASTHelpers.getSymbol(subTree.getExpression());
if (method.isStatic() && lhsSymbol instanceof VarSymbol) {
return Optional.empty();
}
if (hasAmbiguousMethodReference(method, state)) {
return Optional.empty();
}
Name lhs = lhsSymbol.name;
if (expectedInstance.isEmpty()) {
return constructFix(lambdaExpr, lhs, subTree.getIdentifier());
}
Type lhsType = lhsSymbol.type;
if (lhsType == null || !expectedInstance.orElseThrow().equals(lhs)) {
return Optional.empty();
}
// XXX: Dropping generic type information is in most cases fine or even more likely to yield a
// valid expression, but in some cases it's necessary to keep them. Maybe return multiple
// variants?
return constructFix(lambdaExpr, lhsType.tsym, subTree.getIdentifier());
}
/**
* Tells whether the given method has an overload that would lead to an ambiguous method
* reference.
*/
private static boolean hasAmbiguousMethodReference(MethodSymbol method, VisitorState state) {
return getOverloads(method)
.anyMatch(m -> haveAmbiguousMethodReferences(m, method, state.getTypes()));
}
/** Returns any overloads of the given method defined on the same class. */
// XXX: This probably doesn't return overloads defined by supertypes. Review and extend if
// necessary.
private static Stream<MethodSymbol> getOverloads(MethodSymbol method) {
return Streams.stream(ASTHelpers.enclosingClass(method).members().getSymbolsByName(method.name))
.filter(MethodSymbol.class::isInstance)
.map(MethodSymbol.class::cast)
.filter(not(method::equals));
}
/** Tells whether method references to the given methods would be mutually ambiguous. */
// XXX: This doesn't necessarily identify all ambiguous cases; carefully read the JLS and update
// this logic if necessary.
private static boolean haveAmbiguousMethodReferences(
MethodSymbol method1, MethodSymbol method2, Types types) {
if (method1.isStatic() == method2.isStatic()) {
return false;
}
if (method1.isStatic()) {
return haveAmbiguousMethodReferences(method2, method1, types);
}
com.sun.tools.javac.util.List<VarSymbol> params1 = method1.params();
com.sun.tools.javac.util.List<VarSymbol> params2 = method2.params();
if (params1.size() != params2.size() - 1) {
return false;
}
// XXX: Here and below: perhaps `isAssignable` is more appropriate than `isConvertible`.
if (!types.isConvertible(method1.owner.asType(), params2.get(0).asType())) {
return false;
}
for (int i = 0; i < params1.size(); i++) {
if (!types.isConvertible(params1.get(0).asType(), params2.get(i + 1).asType())) {
return false;
}
}
return true;
}
/**
* Attempts to match the given method invocation's arguments against the rightmost parameters of
* the provided lambda expression, in order; if successful with zero or one lambda parameter
* unaccounted for, then said parameter is returned.
*/
// XXX: Refactor or replace inner `Optional` with a custom type.
@SuppressWarnings("NestedOptionals")
private static Optional<Optional<Name>> matchParameters(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
ImmutableList<Name> expectedArguments = getVariables(lambdaExpr);
List<? extends ExpressionTree> args = subTree.getArguments();
int diff = expectedArguments.size() - args.size();
if (diff < 0 || diff > 1) {
return Optional.empty();
}
for (int i = 0; i < args.size(); i++) {
ExpressionTree arg = args.get(i);
if (arg.getKind() != Kind.IDENTIFIER
|| !((IdentifierTree) arg).getName().equals(expectedArguments.get(i + diff))) {
return Optional.empty();
}
}
return Optional.of(diff == 0 ? Optional.empty() : Optional.of(expectedArguments.get(0)));
}
private static ImmutableList<Name> getVariables(LambdaExpressionTree tree) {
return tree.getParameters().stream().map(VariableTree::getName).collect(toImmutableList());
}
private static Optional<SuggestedFix.Builder> constructFix(
LambdaExpressionTree lambdaExpr, Symbol target, Object methodName) {
Name sName = target.getSimpleName();
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
if (!"java.lang".equals(target.packge().getQualifiedName().toString())) {
// XXX: Check whether the type can be imported. If not, skip the suggestion. (In other words:
// if another type with simple name `sName` is already imported, then this suggested fix would
// introduce a compilation failure.)
// XXX: Make sure `SuggestedFixes.qualifyType` handles `java.lang` and the double-import case,
// then use that method.
Name fqName = target.getQualifiedName();
if (!sName.equals(fqName)) {
return fix.map(b -> b.addImport(fqName.toString()));
}
}
return fix;
}
// XXX: As-is this method shouldn't return an `Optional`.
private static Optional<SuggestedFix.Builder> constructFix(
LambdaExpressionTree lambdaExpr, Object target, Object methodName) {
return Optional.of(SuggestedFix.builder().replace(lambdaExpr, target + "::" + methodName));
}
private static boolean tryCompileWithFix(VisitorState state, SuggestedFix fix) {
return !IS_NESTED_INVOCATION
&& SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true);
}
}

View File

@@ -1,205 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
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.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import java.util.List;
import java.util.Optional;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} which flags lambda expressions that can be replaced with method references.
*/
// XXX: Other custom expressions we could rewrite:
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
// - `(a, b) -> a + b` to `String::concat` or `{Integer,Long,Float,Double}::sum`. Also requires null
// checking.
// - `i -> new int[i]` to `int[]::new`.
// - `() -> new Foo()` to `Foo::new` (and variations).
// XXX: Link to Effective Java, Third Edition, Item 43. In there the suggested approach is not so
// black-and-white. Maybe we can more closely approximate it?
// XXX: With Java 9's introduction of `Predicate.not`, we could write many lambda expressions to
// `not(some::reference)`.
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
@AutoService(BugChecker.class)
@BugPattern(
name = "MethodReferenceUsage",
summary = "Prefer method references over lambda expressions",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class MethodReferenceUsageCheck extends BugChecker
implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
/*
* Lambda expressions can be used in several places where method references cannot, either
* because the latter are not syntactically valid or ambiguous. Rather than encoding all these
* edge cases we try to compile the code with the suggested fix, to see whether this works.
*/
return constructMethodRef(tree, tree.getBody())
.map(SuggestedFix.Builder::build)
.filter(
fix ->
SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, Tree subTree) {
switch (subTree.getKind()) {
case BLOCK:
return constructMethodRef(lambdaExpr, (BlockTree) subTree);
case EXPRESSION_STATEMENT:
return constructMethodRef(lambdaExpr, ((ExpressionStatementTree) subTree).getExpression());
case METHOD_INVOCATION:
return constructMethodRef(lambdaExpr, (MethodInvocationTree) subTree);
case PARENTHESIZED:
return constructMethodRef(lambdaExpr, ((ParenthesizedTree) subTree).getExpression());
case RETURN:
return constructMethodRef(lambdaExpr, ((ReturnTree) subTree).getExpression());
default:
return Optional.empty();
}
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, BlockTree subTree) {
return Optional.of(subTree.getStatements())
.filter(statements -> statements.size() == 1)
.flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0)));
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
return matchArguments(lambdaExpr, subTree)
.flatMap(expectedInstance -> constructMethodRef(lambdaExpr, subTree, expectedInstance));
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr,
MethodInvocationTree subTree,
Optional<Name> expectedInstance) {
ExpressionTree methodSelect = subTree.getMethodSelect();
switch (methodSelect.getKind()) {
case IDENTIFIER:
if (expectedInstance.isPresent()) {
/* Direct method call; there is no matching "implicit parameter". */
return Optional.empty();
}
Symbol sym = ASTHelpers.getSymbol(methodSelect);
if (!sym.isStatic()) {
return constructFix(lambdaExpr, "this", methodSelect);
}
return constructFix(lambdaExpr, sym.owner, methodSelect);
case MEMBER_SELECT:
return constructMethodRef(lambdaExpr, (MemberSelectTree) methodSelect, expectedInstance);
default:
throw new VerifyException("Unexpected type of expression: " + methodSelect.getKind());
}
}
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, MemberSelectTree subTree, Optional<Name> expectedInstance) {
if (subTree.getExpression().getKind() != Kind.IDENTIFIER) {
// XXX: Could be parenthesized. Handle. Also in other classes.
/*
* Only suggest a replacement if the method select's expression provably doesn't have
* side-effects. Otherwise the replacement may not be behavior preserving.
*/
return Optional.empty();
}
Name lhs = ((IdentifierTree) subTree.getExpression()).getName();
if (expectedInstance.isEmpty()) {
return constructFix(lambdaExpr, lhs, subTree.getIdentifier());
}
Type lhsType = ASTHelpers.getType(subTree.getExpression());
if (lhsType == null || !expectedInstance.orElseThrow().equals(lhs)) {
return Optional.empty();
}
// XXX: Dropping generic type information is in most cases fine or even more likely to yield a
// valid expression, but in some cases it's necessary to keep them. Maybe return multiple
// variants?
return constructFix(lambdaExpr, lhsType.tsym, subTree.getIdentifier());
}
private static Optional<Optional<Name>> matchArguments(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
ImmutableList<Name> expectedArguments = getVariables(lambdaExpr);
List<? extends ExpressionTree> args = subTree.getArguments();
int diff = expectedArguments.size() - args.size();
if (diff < 0 || diff > 1) {
return Optional.empty();
}
for (int i = 0; i < args.size(); i++) {
ExpressionTree arg = args.get(i);
if (arg.getKind() != Kind.IDENTIFIER
|| !((IdentifierTree) arg).getName().equals(expectedArguments.get(i + diff))) {
return Optional.empty();
}
}
return Optional.of(diff == 0 ? Optional.empty() : Optional.of(expectedArguments.get(0)));
}
private static ImmutableList<Name> getVariables(LambdaExpressionTree tree) {
return tree.getParameters().stream().map(VariableTree::getName).collect(toImmutableList());
}
private static Optional<SuggestedFix.Builder> constructFix(
LambdaExpressionTree lambdaExpr, Symbol target, Object methodName) {
Name sName = target.getSimpleName();
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
if (!"java.lang".equals(target.packge().toString())) {
Name fqName = target.getQualifiedName();
if (!sName.equals(fqName)) {
return fix.map(b -> b.addImport(fqName.toString()));
}
}
return fix;
}
private static Optional<SuggestedFix.Builder> constructFix(
LambdaExpressionTree lambdaExpr, Object target, Object methodName) {
return Optional.of(SuggestedFix.builder().replace(lambdaExpr, target + "::" + methodName));
}
}

View File

@@ -24,12 +24,11 @@ import com.sun.source.tree.Tree;
/** A {@link BugChecker} that flags likely missing Refaster annotations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "MissingRefasterAnnotation",
summary = "The Refaster template contains a method without any Refaster annotations",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class MissingRefasterAnnotationCheck extends BugChecker implements ClassTreeMatcher {
public final class MissingRefasterAnnotation extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> REFASTER_ANNOTATION =
annotations(

View File

@@ -17,6 +17,7 @@ import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags method invocations for which all arguments are wrapped using
@@ -24,12 +25,11 @@ import java.util.List;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "MockitoStubbing",
summary = "Don't unnecessarily use Mockito's `eq(...)`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoStubbingCheck extends BugChecker implements MethodInvocationTreeMatcher {
public final class MockitoStubbing extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
@@ -45,7 +45,7 @@ public final class MockitoStubbingCheck extends BugChecker implements MethodInvo
for (ExpressionTree arg : arguments) {
suggestedFix.replace(
arg,
Util.treeToString(
SourceCode.treeToString(
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
}

View File

@@ -0,0 +1,51 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.List;
import java.util.Optional;
/** A {@link BugChecker} which flags nesting of {@link Optional Optionals}. */
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Avoid nesting `Optional`s inside `Optional`s; the resultant code is hard to reason about",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
Type optionalType = OPTIONAL.get(state);
Type type = ASTHelpers.getType(tree);
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
return false;
}
List<Type> typeArguments = type.getTypeArguments();
return !typeArguments.isEmpty()
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
}
}

View File

@@ -6,7 +6,6 @@ import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import com.google.auto.service.AutoService;
@@ -32,6 +31,7 @@ import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code Comparator#comparing*} invocations that can be replaced
@@ -41,15 +41,13 @@ import java.util.stream.Stream;
// specific types.
@AutoService(BugChecker.class)
@BugPattern(
name = "PrimitiveComparison",
summary =
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
+ " of the provided function",
linkType = NONE,
severity = WARNING,
tags = PERFORMANCE)
public final class PrimitiveComparisonCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class PrimitiveComparison extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
anyOf(
@@ -85,16 +83,15 @@ public final class PrimitiveComparisonCheck extends BugChecker
private static Optional<Fix> attemptMethodInvocationReplacement(
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(methodSymbol -> methodSymbol.getSimpleName().toString())
.flatMap(
actualMethodName ->
Optional.of(getPreferredMethod(cmpType, isStatic, state))
.filter(not(actualMethodName::equals)))
.map(
preferredMethodName ->
prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state))
.map(preferredMethodName -> suggestFix(tree, preferredMethodName, state));
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
String preferredMethodName = getPreferredMethod(cmpType, isStatic, state);
if (actualMethodName.equals(preferredMethodName)) {
return Optional.empty();
}
return Optional.of(
suggestFix(
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state));
}
/**
@@ -116,7 +113,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
String typeArguments =
Stream.concat(
Stream.of(Util.treeToString(tree.getTypeArguments().get(0), state)),
Stream.of(SourceCode.treeToString(tree.getTypeArguments().get(0), state)),
Stream.of(cmpType.tsym.getSimpleName())
.filter(u -> "comparing".equals(preferredMethodName)))
.collect(joining(", ", "<", ">"));
@@ -171,7 +168,7 @@ public final class PrimitiveComparisonCheck extends BugChecker
case MEMBER_SELECT:
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
return SuggestedFix.replace(
ms, Util.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
ms, SourceCode.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
default:
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
}

View File

@@ -41,16 +41,17 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags redundant explicit string conversions. */
@AutoService(BugChecker.class)
@BugPattern(
name = "RedundantStringConversion",
summary = "Avoid redundant string conversions when possible",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RedundantStringConversionCheck extends BugChecker
public final class RedundantStringConversion extends BugChecker
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String FLAG_PREFIX = "RedundantStringConversion:";
@@ -157,17 +158,17 @@ public final class RedundantStringConversionCheck extends BugChecker
private final Matcher<ExpressionTree> conversionMethodMatcher;
/** Instantiates the default {@link RedundantStringConversionCheck}. */
public RedundantStringConversionCheck() {
/** Instantiates the default {@link RedundantStringConversion}. */
public RedundantStringConversion() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link RedundantStringConversionCheck}.
* Instantiates a customized {@link RedundantStringConversion}.
*
* @param flags Any provided command line flags.
*/
public RedundantStringConversionCheck(ErrorProneFlags flags) {
public RedundantStringConversion(ErrorProneFlags flags) {
conversionMethodMatcher = createConversionMethodMatcher(flags);
}
@@ -326,7 +327,7 @@ public final class RedundantStringConversionCheck extends BugChecker
return trySimplify(tree, state, filter)
.map(
replacement ->
SuggestedFix.builder().replace(tree, Util.treeToString(replacement, state)));
SuggestedFix.builder().replace(tree, SourceCode.treeToString(replacement, state)));
}
private Optional<ExpressionTree> trySimplify(
@@ -350,7 +351,7 @@ public final class RedundantStringConversionCheck extends BugChecker
default:
throw new IllegalStateException(
"Cannot simplify method call with two or more arguments: "
+ Util.treeToString(tree, state));
+ SourceCode.treeToString(tree, state));
}
}
@@ -363,7 +364,7 @@ public final class RedundantStringConversionCheck extends BugChecker
return Optional.of(methodInvocation.getMethodSelect())
.filter(methodSelect -> methodSelect.getKind() == Kind.MEMBER_SELECT)
.map(methodSelect -> ((MemberSelectTree) methodSelect).getExpression())
.filter(expr -> !"super".equals(Util.treeToString(expr, state)));
.filter(expr -> !"super".equals(SourceCode.treeToString(expr, state)));
}
private static Optional<ExpressionTree> trySimplifyUnaryMethod(

View File

@@ -16,6 +16,7 @@ import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.Refaster;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags unnecessary {@link Refaster#anyOf(Object[])} usages.
@@ -25,13 +26,11 @@ import com.sun.source.tree.MethodInvocationTree;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "RefasterAnyOfUsage",
summary = "`Refaster#anyOf` should be passed at least two parameters",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RefasterAnyOfUsageCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
@@ -46,7 +45,8 @@ public final class RefasterAnyOfUsageCheck extends BugChecker
case 1:
return describeMatch(
tree,
SuggestedFix.replace(tree, Util.treeToString(tree.getArguments().get(0), state)));
SuggestedFix.replace(
tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
default:
/* Handled below. */
}

View File

@@ -30,12 +30,11 @@ import com.sun.source.tree.Tree;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "RequestMappingAnnotation",
summary = "Make sure all `@RequestMapping` method parameters are annotated",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class RequestMappingAnnotationCheck extends BugChecker implements MethodTreeMatcher {
public final class RequestMappingAnnotation extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
// XXX: Generalize this logic to fully support Spring meta-annotations, then update the class
@@ -64,6 +63,7 @@ public final class RequestMappingAnnotationCheck extends BugChecker implements M
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "PathVariable"),
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
isType(ANN_PACKAGE_PREFIX + "RequestParam"))),

View File

@@ -24,12 +24,11 @@ import com.sun.source.tree.VariableTree;
/** A {@link BugChecker} which flags {@code @RequestParam} parameters with an unsupported type. */
@AutoService(BugChecker.class)
@BugPattern(
name = "RequestParamType",
summary = "`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class RequestParamTypeCheck extends BugChecker implements VariableTreeMatcher {
public final class RequestParamType extends BugChecker implements VariableTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<VariableTree> HAS_UNSUPPORTED_REQUEST_PARAM =
allOf(

View File

@@ -32,12 +32,11 @@ import com.sun.source.tree.Tree;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "ScheduledTransactionTrace",
summary = "Scheduled operation must start a new New Relic transaction",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ScheduledTransactionTraceCheck extends BugChecker implements MethodTreeMatcher {
public final class ScheduledTransactionTrace extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TRACE_ANNOTATION_FQCN = "com.newrelic.api.agent.Trace";
private static final Matcher<Tree> IS_SCHEDULED =
@@ -75,7 +74,7 @@ public final class ScheduledTransactionTraceCheck extends BugChecker implements
return describeMatch(
traceAnnotation,
SuggestedFixes.updateAnnotationArgumentValues(
traceAnnotation, "dispatcher", ImmutableList.of("true"))
traceAnnotation, state, "dispatcher", ImmutableList.of("true"))
.build());
}

View File

@@ -22,6 +22,7 @@ import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree.Kind;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags SLF4J usages that are likely to be in error. */
// XXX: The special-casing of Throwable applies only to SLF4J 1.6.0+; see
@@ -31,13 +32,11 @@ import java.util.Optional;
// preconditions, ...
@AutoService(BugChecker.class)
@BugPattern(
name = "Slf4jLogStatement",
summary = "Make sure SLF4J log statements contain proper placeholders with matching arguments",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class Slf4jLogStatementCheck extends BugChecker
implements MethodInvocationTreeMatcher {
public final class Slf4jLogStatement extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
@@ -114,7 +113,7 @@ public final class Slf4jLogStatementCheck extends BugChecker
* replaced at this usage site.
*/
description.addFix(
SuggestedFix.replace(tree, Util.treeToString(tree, state).replace("%s", "{}")));
SuggestedFix.replace(tree, SourceCode.treeToString(tree, state).replace("%s", "{}")));
}
return false;

View File

@@ -25,6 +25,8 @@ import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree.Kind;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code @RequestMapping} annotations that can be written more
@@ -32,13 +34,12 @@ import java.util.Optional;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "SpringMvcAnnotation",
summary =
"Prefer the conciseness of `@{Get,Put,Post,Delete,Patch}Mapping` over `@RequestMapping`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class SpringMvcAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
public final class SpringMvcAnnotation extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
@@ -92,7 +93,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
private static String extractMethod(ExpressionTree expr, VisitorState state) {
switch (expr.getKind()) {
case IDENTIFIER:
return Util.treeToString(expr, state);
return SourceCode.treeToString(expr, state);
case MEMBER_SELECT:
return ((MemberSelectTree) expr).getIdentifier().toString();
default:
@@ -105,7 +106,7 @@ public final class SpringMvcAnnotationCheck extends BugChecker implements Annota
String newArguments =
tree.getArguments().stream()
.filter(not(argToRemove::equals))
.map(arg -> Util.treeToString(arg, state))
.map(arg -> SourceCode.treeToString(arg, state))
.collect(joining(", "));
return SuggestedFix.builder()

View File

@@ -45,12 +45,11 @@ import java.util.Optional;
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
@AutoService(BugChecker.class)
@BugPattern(
name = "StaticImport",
summary = "Identifier should be statically imported",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class StaticImportCheck extends BugChecker implements MemberSelectTreeMatcher {
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
private static final long serialVersionUID = 1L;
/**
@@ -67,6 +66,7 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
"com.google.errorprone.BugPattern.LinkType",
"com.google.errorprone.BugPattern.SeverityLevel",
"com.google.errorprone.BugPattern.StandardTags",
"com.google.errorprone.matchers.Matchers",
"com.google.errorprone.refaster.ImportPolicy",
"com.mongodb.client.model.Accumulators",
"com.mongodb.client.model.Aggregates",

View File

@@ -29,13 +29,12 @@ import java.time.LocalTime;
/** A {@link BugChecker} which flags illegal time-zone related operations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "TimeZoneUsage",
summary =
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class TimeZoneUsageCheck extends BugChecker implements MethodInvocationTreeMatcher {
public final class TimeZoneUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> BANNED_TIME_METHOD =
anyOf(
@@ -59,7 +58,7 @@ public final class TimeZoneUsageCheck extends BugChecker implements MethodInvoca
LocalDateTime.class.getName(),
LocalTime.class.getName())
.named("now"),
staticMethod().onClassAny(Instant.class.getName()).named("now").withParameters());
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
@@ -28,7 +28,7 @@ import java.util.stream.Stream;
* <p>This class allows one to define a whitelist or blacklist of annotations or their attributes.
* Annotations are identified by their fully qualified name.
*/
final class AnnotationAttributeMatcher implements Serializable {
public final class AnnotationAttributeMatcher implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean complement;
@@ -59,7 +59,7 @@ final class AnnotationAttributeMatcher implements Serializable {
* @param exclusions The listed annotations or annotation attributes are not matched.
* @return A non-{@code null} {@link AnnotationAttributeMatcher}.
*/
static AnnotationAttributeMatcher create(
public static AnnotationAttributeMatcher create(
Optional<? extends List<String>> inclusions, Iterable<String> exclusions) {
Set<String> includedWholeTypes = new HashSet<>();
Set<String> excludedWholeTypes = new HashSet<>();
@@ -97,7 +97,13 @@ final class AnnotationAttributeMatcher implements Serializable {
}
}
Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
/**
* Returns the subset of arguments of the given {@link AnnotationTree} matched by this instance.
*
* @param tree The annotation AST node to be inspected.
* @return Any matching annotation arguments.
*/
public Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
Type type = ASTHelpers.getType(tree.getAnnotationType());
if (type == null) {
return Stream.empty();

View File

@@ -1,9 +1,10 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
final class JavaKeywords {
/** Utility class that can be used to identify reserved keywords of the Java language. */
public final class JavaKeywords {
/**
* List of all reserved keywords in the Java language.
*
@@ -95,17 +96,33 @@ final class JavaKeywords {
private JavaKeywords() {}
/** Tells whether the given string is a reserved keyword in the Java language. */
/**
* Tells whether the given string is a reserved keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved keyword in the Java language.
*/
public static boolean isReservedKeyword(String str) {
return RESERVED_KEYWORDS.contains(str);
}
/** Tells whether the given string is a contextual keyword in the Java language. */
/**
* Tells whether the given string is a contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a contextual keyword in the Java language.
*/
public static boolean isContextualKeyword(String str) {
return CONTEXTUAL_KEYWORDS.contains(str);
}
/** Tells whether the given string is a reserved or contextual keyword in the Java language. */
/**
* Tells whether the given string is a reserved or contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved or contextual keyword in the Java
* language.
*/
public static boolean isKeyword(String str) {
return ALL_KEYWORDS.contains(str);
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
@@ -15,13 +15,20 @@ import java.util.regex.Pattern;
/** A method invocation expression {@link Matcher} factory. */
// XXX: Document better. The expressions accepted here could also be defined using `MethodMatchers`.
// So explain why this class is still useful.
final class MethodMatcherFactory {
public final class MethodMatcherFactory {
private static final Splitter ARGUMENT_TYPE_SPLITTER =
Splitter.on(',').trimResults().omitEmptyStrings();
private static final Pattern METHOD_SIGNATURE =
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
Matcher<ExpressionTree> create(Collection<String> signatures) {
/**
* Creates a {@link Matcher} of methods with any of the given signatures.
*
* @param signatures The method signatures of interest.
* @return A new {@link Matcher} which accepts invocation expressions of any method identified by
* the given signatures.
*/
public Matcher<ExpressionTree> create(Collection<String> signatures) {
return anyOf(
signatures.stream()
.map(MethodMatcherFactory::createMethodMatcher)

View File

@@ -1,19 +1,26 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.Tree;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
* AST nodes.
*/
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
final class Util {
private Util() {}
public final class SourceCode {
private SourceCode() {}
/**
* Returns a string representation of the given {@link Tree}, preferring the original source code
* (if available) over its prettified representation.
*
* @param tree The AST node of interest.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
* found.
* @return A non-{@code null} string.
*/
static String treeToString(Tree tree, VisitorState state) {
public static String treeToString(Tree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}

View File

@@ -0,0 +1,4 @@
/** Auxiliary utilities for use by Error Prone checks. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
package tech.picnic.errorprone.bugpatterns.util;

View File

@@ -59,6 +59,27 @@ final class AssertJThrowingCallableTemplates {
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
@@ -148,6 +169,27 @@ final class AssertJThrowingCallableTemplates {
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
@@ -235,6 +277,27 @@ final class AssertJThrowingCallableTemplates {
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatNullPointerException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
@@ -268,9 +331,9 @@ final class AssertJThrowingCallableTemplates {
}
static final class AssertThatThrownByIOExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message);
}
@@ -284,6 +347,25 @@ final class AssertJThrowingCallableTemplates {
}
}
static final class AssertThatThrownByIOExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IOException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownBy {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(
@@ -321,6 +403,32 @@ final class AssertJThrowingCallableTemplates {
}
}
static final class AssertThatThrownByHasMessageParameters {
@BeforeTemplate
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatExceptionOfType(exceptionType)
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(exceptionType)
.hasMessage(message, parameters);
}
}
// XXX: Drop this template in favour of a generic Error Prone check which flags
// `String.format(...)` arguments to a wide range of format methods.
static final class AbstractThrowableAssertHasMessage {

View File

@@ -15,6 +15,7 @@ import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.IntFunction;
import java.util.stream.Stream;
@@ -129,31 +130,32 @@ final class CollectionTemplates {
}
}
static final class CollectionRemoveAllFromCollectionBlock<T, S extends T> {
static final class SetRemoveAllCollection<T, S extends T> {
@BeforeTemplate
void before(Collection<T> removeTo, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeTo::remove);
void before(Set<T> removeFrom, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeFrom::remove);
}
@BeforeTemplate
void before2(Collection<T> removeTo, Collection<S> elementsToRemove) {
void before2(Set<T> removeFrom, Collection<S> elementsToRemove) {
for (T element : elementsToRemove) {
removeTo.remove(element);
removeFrom.remove(element);
}
}
// XXX: This method is identical to `before2` except for the loop type. Make Refaster smarter so
// that this is supported out of the box.
// that this is supported out of the box. After doing so, also drop the `S extends T` type
// constraint; ideally this check applies to any `S`.
@BeforeTemplate
void before3(Collection<T> removeTo, Collection<S> elementsToRemove) {
void before3(Set<T> removeFrom, Collection<S> elementsToRemove) {
for (S element : elementsToRemove) {
removeTo.remove(element);
removeFrom.remove(element);
}
}
@AfterTemplate
void after(Collection<T> removeTo, Collection<S> elementsToRemove) {
removeTo.removeAll(elementsToRemove);
void after(Set<T> removeFrom, Collection<S> elementsToRemove) {
removeFrom.removeAll(elementsToRemove);
}
}

View File

@@ -20,7 +20,7 @@ final class EqualityTemplates {
* remaining reference-based equality checks.
*/
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
// work around the issue by selecting the "largest replacements". See RefasterCheck.
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
@BeforeTemplate
boolean before(T a, T b) {
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
@@ -34,9 +34,8 @@ final class EqualityTemplates {
}
/** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsageCheck` tries to
// achieve. If/when `MethodReferenceUsageCheck` becomes production ready, we should simply drop
// this check.
// 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.
// XXX: Alternatively, the rule should be replaced with a plugin which also identifies cases where
// the arguments are swapped but simplification is possible anyway, by virtue of `v` being
// non-null.

View File

@@ -92,7 +92,7 @@ final class ImmutableListMultimapTemplates {
*/
static final class EntryToImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
ImmutableListMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().put(entry).build(),
Stream.of(entry)

View File

@@ -192,7 +192,7 @@ final class ImmutableMapTemplates {
// XXX: Instead of `Map.Entry::getKey` we could also match `e -> e.getKey()`. But for some
// reason Refaster doesn't handle that case. This doesn't matter if we roll out use of
// `MethodReferenceUsageCheck`. Same observation applies to a lot of other Refaster checks.
// `MethodReferenceUsage`. Same observation applies to a lot of other Refaster checks.
@BeforeTemplate
@SuppressWarnings("NullAway")
ImmutableMap<K, V2> before(Map<K, V1> map) {

View File

@@ -4,7 +4,6 @@ import static com.google.common.collect.ImmutableSortedMultiset.toImmutableSorte
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.Refaster;
@@ -91,14 +90,14 @@ final class ImmutableSortedMultisetTemplates {
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedMultiset<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableMultiset<T> before(T[] iterable) {
ImmutableSortedMultiset<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.<T>naturalOrder().add(iterable).build(),
Arrays.stream(iterable).collect(toImmutableSortedMultiset(naturalOrder())));
}
@BeforeTemplate
ImmutableMultiset<T> before(Iterator<T> iterable) {
ImmutableSortedMultiset<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),
@@ -106,7 +105,7 @@ final class ImmutableSortedMultisetTemplates {
}
@BeforeTemplate
ImmutableMultiset<T> before(Iterable<T> iterable) {
ImmutableSortedMultiset<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),

View File

@@ -4,7 +4,6 @@ import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.Refaster;
@@ -89,14 +88,14 @@ final class ImmutableSortedSetTemplates {
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedSet<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSet<T> before(T[] iterable) {
ImmutableSortedSet<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedSet.<T>naturalOrder().add(iterable).build(),
Arrays.stream(iterable).collect(toImmutableSortedSet(naturalOrder())));
}
@BeforeTemplate
ImmutableSet<T> before(Iterator<T> iterable) {
ImmutableSortedSet<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),
@@ -104,7 +103,7 @@ final class ImmutableSortedSetTemplates {
}
@BeforeTemplate
ImmutableSet<T> before(Iterable<T> iterable) {
ImmutableSortedSet<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),

View File

@@ -113,7 +113,7 @@ final class OptionalTemplates {
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Maybe our RefasterCheck should test `compilesWithFix`?
// Maybe our `Refaster` checker should test `compilesWithFix`?
abstract static class TernaryOperatorOptionalPositiveFiltering<T> {
@Placeholder
abstract boolean test(T value);
@@ -133,7 +133,7 @@ final class OptionalTemplates {
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Maybe our RefasterCheck should test `compilesWithFix`?
// Maybe our `Refaster` checker should test `compilesWithFix`?
abstract static class TernaryOperatorOptionalNegativeFiltering<T> {
@Placeholder
abstract boolean test(T value);
@@ -166,6 +166,11 @@ final class OptionalTemplates {
}
}
/**
* Prefer {@link Optional#map} over a {@link Optional#flatMap} which wraps the result of a
* transformation in an {@link Optional}; the former operation transforms {@code null} to {@link
* Optional#empty()}.
*/
abstract static class MapToNullable<T, S> {
@Placeholder
abstract S toNullableFunction(@MayOptionallyUse T element);
@@ -234,9 +239,16 @@ final class OptionalTemplates {
}
}
/** Within a stream's map operation unconditional {@link Optional#get()} calls can be avoided. */
// XXX: An alternative approach is to `.flatMap(Optional::stream)`. That may be a bit longer, but
// yield nicer code. Think about it.
/**
* Within a stream's map operation unconditional {@link Optional#orElseThrow()} calls can be
* avoided.
*
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving. The
* original code throws an exception if the mapping operation does not produce a value, while the
* replacement does not.
*/
// XXX: An alternative approach is to use `.flatMap(Optional::stream)`. That may be a bit longer,
// but yields nicer code. Think about it.
abstract static class StreamMapToOptionalGet<T, S> {
@Placeholder
abstract Optional<S> toOptionalFunction(@MayOptionallyUse T element);
@@ -306,6 +318,7 @@ final class OptionalTemplates {
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
abstract static class OptionalOrOtherOptional<T> {
@BeforeTemplate
@SuppressWarnings("NestedOptionals" /* Auto-fix for the `NestedOptionals` check. */)
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
// XXX: Note that rewriting the first and third variant will change the code's behavior if
// `optional2` has side-effects.

View File

@@ -6,14 +6,14 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AmbiguousJsonCreatorCheckTest {
final class AmbiguousJsonCreatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AmbiguousJsonCreatorCheck.class, getClass())
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X",
containsPattern("`JsonCreator.Mode` should be set for single-argument creators"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreatorCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass());
@Test
void identification() {
@@ -112,7 +112,6 @@ final class AmbiguousJsonCreatorCheckTest {
" return new F(s);",
" }",
" }",
"",
"}")
.doTest();
}

View File

@@ -6,11 +6,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AssertJIsNullCheckTest {
final class AssertJIsNullTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AssertJIsNullCheck.class, getClass());
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNullCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass());
@Test
void identification() {

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AutowiredConstructorCheckTest {
final class AutowiredConstructorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AutowiredConstructorCheck.class, getClass());
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructorCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass());
@Test
void identification() {
@@ -27,27 +27,34 @@ final class AutowiredConstructorCheckTest {
" }",
"",
" class B {",
" @Autowired void setProperty(Object o) {}",
" @Autowired",
" void setProperty(Object o) {}",
" }",
"",
" class C {",
" // BUG: Diagnostic contains:",
" @Autowired C() {}",
" @Autowired",
" C() {}",
" }",
"",
" class D {",
" // BUG: Diagnostic contains:",
" @Autowired D(String x) {}",
" @Autowired",
" D(String x) {}",
" }",
"",
" class E {",
" @Autowired E() {}",
" @Autowired",
" E() {}",
"",
" E(String x) {}",
" }",
"",
" class F {",
" F() {}",
" @Autowired F(String x) {}",
"",
" @Autowired",
" F(String x) {}",
" }",
"",
" class G {",
@@ -55,7 +62,8 @@ final class AutowiredConstructorCheckTest {
" }",
"",
" class H {",
" @SafeVarargs H(List<String>... lists) {}",
" @SafeVarargs",
" H(List<String>... lists) {}",
" }",
"}")
.doTest();
@@ -70,11 +78,14 @@ final class AutowiredConstructorCheckTest {
"",
"interface Container {",
" class A {",
" @Autowired @Deprecated A() {}",
" @Autowired",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
" @Autowired B(String x) {}",
" @Autowired",
" B(String x) {}",
" }",
"}")
.addOutputLines(
@@ -83,10 +94,13 @@ final class AutowiredConstructorCheckTest {
"",
"interface Container {",
" class A {",
" @Deprecated A() {}",
"",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
"",
" B(String x) {}",
" }",
"}")

View File

@@ -1,156 +0,0 @@
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 CanonicalAnnotationSyntaxCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntaxCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntaxCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" int[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo A minimal1();",
" @A.Foo A minimal2();",
" @Foo A minimal3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo() A functional1();",
" // BUG: Diagnostic contains:",
" @A.Foo() A functional2();",
" // BUG: Diagnostic contains:",
" @Foo() A functional3();",
"",
" @pkg.A.Foo(1) A simple1();",
" @A.Foo(1) A simple2();",
" @Foo(1) A simple3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1}) A singleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1}) A singleton2();",
" // BUG: Diagnostic contains:",
" @Foo({1}) A singleton3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value = 1) A verbose1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value = 1) A verbose2();",
" // BUG: Diagnostic contains:",
" @Foo(value = 1) A verbose3();",
"",
" @pkg.A.Foo(value2 = 2) A custom1();",
" @A.Foo(value2 = 2) A custom2();",
" @Foo(value2 = 2) A custom3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value2 = {2}) A customSingleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value2 = {2}) A customSingleton2();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {2}) A customSingleton3();",
"",
" @pkg.A.Foo(value2 = {2, 2}) A customPair1();",
" @A.Foo(value2 = {2, 2}) A customPair2();",
" @Foo(value2 = {2, 2}) A customPair3();",
"",
" @pkg.A.Foo(value = 1, value2 = 2) A extended1();",
" @A.Foo(value = 1, value2 = 2) A extended2();",
" @Foo(value = 1, value2 = 2) A extended3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1, 1,}) A trailingComma1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1, 1,}) A trailingComma2();",
" // BUG: Diagnostic contains:",
" @Foo({1, 1,}) A trailingComma3();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo() A functional1();",
" @A.Foo() A functional2();",
" @Foo() A functional3();",
"",
" @pkg.A.Foo(value = \"foo\") A verbose1();",
" @A.Foo(value = \"a'b\") A verbose2();",
" @Foo(value = \"a\" + \"\\nb\") A verbose3();",
"",
" @pkg.A.Foo(value = {\"foo\"}) A moreVerbose1();",
" @A.Foo(value = {\"a'b\"}) A moreVerbose2();",
" @Foo(value = {\"a\" + \"\\nb\"}) A moreVerbose3();",
"",
" @pkg.A.Foo(value = {\"foo\", \"bar\"}, value2 = {2}) A extended1();",
" @A.Foo(value = {\"a'b\", \"c'd\"}, value2 = {2}) A extended2();",
" @Foo(value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"}, value2 = {2}) A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\",}) A trailingComma1();",
" @A.Foo({\"a'b\", \"c'd\",}) A trailingComma2();",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\",}) A trailingComma3();",
"}")
.addOutputLines(
"out/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo A functional1();",
" @A.Foo A functional2();",
" @Foo A functional3();",
"",
" @pkg.A.Foo(\"foo\") A verbose1();",
" @A.Foo(\"a'b\") A verbose2();",
" @Foo(\"a\" + \"\\nb\") A verbose3();",
"",
" @pkg.A.Foo(\"foo\") A moreVerbose1();",
" @A.Foo(\"a'b\") A moreVerbose2();",
" @Foo(\"a\" + \"\\nb\") A moreVerbose3();",
"",
" @pkg.A.Foo(value = {\"foo\", \"bar\"}, value2 = 2) A extended1();",
" @A.Foo(value = {\"a'b\", \"c'd\"}, value2 = 2) A extended2();",
" @Foo(value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"}, value2 = 2) A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\"}) A trailingComma1();",
" @A.Foo({\"a'b\", \"c'd\"}) A trailingComma2();",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\"}) A trailingComma3();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,274 @@
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 CanonicalAnnotationSyntaxTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" int[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo",
" A minimal1();",
"",
" @A.Foo",
" A minimal2();",
"",
" @Foo",
" A minimal3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo()",
" A functional1();",
" // BUG: Diagnostic contains:",
" @A.Foo()",
" A functional2();",
" // BUG: Diagnostic contains:",
" @Foo()",
" A functional3();",
"",
" @pkg.A.Foo(1)",
" A simple1();",
"",
" @A.Foo(1)",
" A simple2();",
"",
" @Foo(1)",
" A simple3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1})",
" A singleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1})",
" A singleton2();",
" // BUG: Diagnostic contains:",
" @Foo({1})",
" A singleton3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value = 1)",
" A verbose1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value = 1)",
" A verbose2();",
" // BUG: Diagnostic contains:",
" @Foo(value = 1)",
" A verbose3();",
"",
" @pkg.A.Foo(value2 = 2)",
" A custom1();",
"",
" @A.Foo(value2 = 2)",
" A custom2();",
"",
" @Foo(value2 = 2)",
" A custom3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value2 = {2})",
" A customSingleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value2 = {2})",
" A customSingleton2();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {2})",
" A customSingleton3();",
"",
" @pkg.A.Foo(value2 = {2, 2})",
" A customPair1();",
"",
" @A.Foo(value2 = {2, 2})",
" A customPair2();",
"",
" @Foo(value2 = {2, 2})",
" A customPair3();",
"",
" @pkg.A.Foo(value = 1, value2 = 2)",
" A extended1();",
"",
" @A.Foo(value = 1, value2 = 2)",
" A extended2();",
"",
" @Foo(value = 1, value2 = 2)",
" A extended3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({",
" 1, 1,",
" })",
" A trailingComma1();",
" // BUG: Diagnostic contains:",
" @A.Foo({",
" 1, 1,",
" })",
" A trailingComma2();",
" // BUG: Diagnostic contains:",
" @Foo({",
" 1, 1,",
" })",
" A trailingComma3();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo()",
" A functional1();",
"",
" @A.Foo()",
" A functional2();",
"",
" @Foo()",
" A functional3();",
"",
" @pkg.A.Foo(value = \"foo\")",
" A verbose1();",
"",
" @A.Foo(value = \"a'b\")",
" A verbose2();",
"",
" @Foo(value = \"a\" + \"\\nb\")",
" A verbose3();",
"",
" @pkg.A.Foo(value = {\"foo\"})",
" A moreVerbose1();",
"",
" @A.Foo(value = {\"a'b\"})",
" A moreVerbose2();",
"",
" @Foo(value = {\"a\" + \"\\nb\"})",
" A moreVerbose3();",
"",
" @pkg.A.Foo(",
" value = {\"foo\", \"bar\"},",
" value2 = {2})",
" A extended1();",
"",
" @A.Foo(",
" value = {\"a'b\", \"c'd\"},",
" value2 = {2})",
" A extended2();",
"",
" @Foo(",
" value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"},",
" value2 = {2})",
" A extended3();",
"",
" @pkg.A.Foo({",
" \"foo\", \"bar\",",
" })",
" A trailingComma1();",
"",
" @A.Foo({",
" \"a'b\", \"c'd\",",
" })",
" A trailingComma2();",
"",
" @Foo({",
" \"a\" + \"\\nb\",",
" \"c\" + \"\\nd\",",
" })",
" A trailingComma3();",
"}")
.addOutputLines(
"out/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo",
" A functional1();",
"",
" @A.Foo",
" A functional2();",
"",
" @Foo",
" A functional3();",
"",
" @pkg.A.Foo(\"foo\")",
" A verbose1();",
"",
" @A.Foo(\"a'b\")",
" A verbose2();",
"",
" @Foo(\"a\" + \"\\nb\")",
" A verbose3();",
"",
" @pkg.A.Foo(\"foo\")",
" A moreVerbose1();",
"",
" @A.Foo(\"a'b\")",
" A moreVerbose2();",
"",
" @Foo(\"a\" + \"\\nb\")",
" A moreVerbose3();",
"",
" @pkg.A.Foo(",
" value = {\"foo\", \"bar\"},",
" value2 = 2)",
" A extended1();",
"",
" @A.Foo(",
" value = {\"a'b\", \"c'd\"},",
" value2 = 2)",
" A extended2();",
"",
" @Foo(",
" value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"},",
" value2 = 2)",
" A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\"})",
" A trailingComma1();",
"",
" @A.Foo({\"a'b\", \"c'd\"})",
" A trailingComma2();",
"",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\"})",
" A trailingComma3();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -7,11 +7,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CollectorMutabilityCheckTest {
final class CollectorMutabilityTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CollectorMutabilityCheck.class, getClass());
CompilationTestHelper.newInstance(CollectorMutability.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CollectorMutabilityCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass());
@Test
void identification() {
@@ -19,12 +19,12 @@ final class CollectorMutabilityCheckTest {
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.util.stream.Collectors.toCollection;",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toSet;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.ArrayList;",
"import java.util.HashMap;",
@@ -137,7 +137,7 @@ final class CollectorMutabilityCheckTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" void m() {",
" Flux.just(1).collect(Collectors.toList());",
" Flux.just(2).collect(toList());",
"",
@@ -148,7 +148,7 @@ final class CollectorMutabilityCheckTest {
"",
" Stream.of(1).collect(Collectors.toSet());",
" Stream.of(2).collect(toSet());",
" }",
" }",
"}")
.addOutputLines(
"A.java",
@@ -165,18 +165,35 @@ final class CollectorMutabilityCheckTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toCollection(ArrayList::new));",
" Flux.just(2).collect(toCollection(ArrayList::new));",
" void m() {",
" Flux.just(1).collect(toCollection(ArrayList::new));",
" Flux.just(2).collect(toCollection(ArrayList::new));",
"",
" Stream.of(\"foo\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> { throw new IllegalStateException(); }, HashMap::new));",
" Stream.of(\"bar\").collect(toMap(String::getBytes, String::length, (a, b) -> { throw new IllegalStateException(); }, HashMap::new));",
" Flux.just(\"baz\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
" Stream.of(\"foo\")",
" .collect(",
" Collectors.toMap(",
" String::getBytes,",
" String::length,",
" (a, b) -> {",
" throw new IllegalStateException();",
" },",
" HashMap::new));",
" Stream.of(\"bar\")",
" .collect(",
" toMap(",
" String::getBytes,",
" String::length,",
" (a, b) -> {",
" throw new IllegalStateException();",
" },",
" HashMap::new));",
" Flux.just(\"baz\")",
" .collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
"",
" Stream.of(1).collect(toCollection(HashSet::new));",
" Stream.of(2).collect(toCollection(HashSet::new));",
" }",
" Stream.of(1).collect(toCollection(HashSet::new));",
" Stream.of(2).collect(toCollection(HashSet::new));",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class EmptyMethodCheckTest {
final class EmptyMethodTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(EmptyMethodCheck.class, getClass());
CompilationTestHelper.newInstance(EmptyMethod.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(EmptyMethodCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());
@Test
void identification() {

View File

@@ -0,0 +1,151 @@
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 ErrorProneTestHelperSourceFormatTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
ErrorProneTestHelperSourceFormat.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" // BUG: Diagnostic contains: No source code provided",
" .addSourceLines(\"A.java\")",
" // BUG: Diagnostic contains: Source code is malformed:",
" .addSourceLines(\"B.java\", \"class B {\")",
" // Well-formed code, so not flagged.",
" .addSourceLines(\"C.java\", \"class C {}\")",
" // Malformed code, but not compile-time constant, so not flagged.",
" .addSourceLines(\"D.java\", \"class D {\" + getClass())",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addSourceLines(\"E.java\", \"class E { }\")",
" .doTest();",
"",
" refactoringTestHelper",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addInputLines(\"in/A.java\", \"class A { }\")",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addOutputLines(\"out/A.java\", \"class A { }\")",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addInputLines(\"in/B.java\", \"import java.util.Map;\", \"\", \"class B {}\")",
" // Unused import, but in an output file, so not flagged.",
" .addOutputLines(\"out/B.java\", \"import java.util.Map;\", \"\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
/*
* Verifies that import sorting and code formatting is performed unconditionally, while unused
* imports are removed unless part of a `BugCheckerRefactoringTestHelper` expected output file.
*/
refactoringTestHelper
.addInputLines(
"in/A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" .addSourceLines(",
" \"A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .doTest();",
"",
" refactoringTestHelper",
" .addInputLines(",
" \"in/A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .addOutputLines(",
" \"out/A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.addOutputLines(
"out/A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" .addSourceLines(",
" \"A.java\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .doTest();",
"",
" refactoringTestHelper",
" .addInputLines(",
" \"in/A.java\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .addOutputLines(",
" \"out/A.java\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -3,23 +3,23 @@ package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ExplicitEnumOrderingCheckTest {
final class ExplicitEnumOrderingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ExplicitEnumOrderingCheck.class, getClass());
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass());
@Test
void Identification() {
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.lang.annotation.RetentionPolicy.SOURCE;",
"import static java.lang.annotation.RetentionPolicy.CLASS;",
"import static java.lang.annotation.RetentionPolicy.RUNTIME;",
"import static java.lang.annotation.RetentionPolicy.SOURCE;",
"import static java.time.chrono.IsoEra.BCE;",
"import static java.time.chrono.IsoEra.CE;",
"",
"import com.google.common.collect.Ordering;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.Ordering;",
"import java.lang.annotation.RetentionPolicy;",
"import java.time.chrono.IsoEra;",
"",

View File

@@ -7,11 +7,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FluxFlatMapUsageCheckTest {
final class FluxFlatMapUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FluxFlatMapUsageCheck.class, getClass());
CompilationTestHelper.newInstance(FluxFlatMapUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(FluxFlatMapUsageCheck.class, getClass());
newInstance(FluxFlatMapUsage.class, getClass());
@Test
void identification() {
@@ -20,8 +20,8 @@ final class FluxFlatMapUsageCheckTest {
"A.java",
"import java.util.function.BiFunction;",
"import java.util.function.Function;",
"import reactor.core.publisher.Mono;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"class A {",
" void m() {",

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FormatStringConcatenationCheckTest {
final class FormatStringConcatenationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FormatStringConcatenationCheck.class, getClass());
CompilationTestHelper.newInstance(FormatStringConcatenation.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenationCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenation.class, getClass());
@Test
void identification() {
@@ -23,8 +23,8 @@ final class FormatStringConcatenationCheckTest {
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.assertj.core.api.SoftAssertions.assertSoftly;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import java.util.Locale;",
"import org.assertj.core.api.Assertions;",
"import org.assertj.core.api.BDDAssertions;",
"import org.assertj.core.api.Fail;",
@@ -269,9 +269,9 @@ final class FormatStringConcatenationCheckTest {
" LOG.error(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.error((Marker) null,\"str \" + toString());",
" LOG.error((Marker) null, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.error((Marker) null,\"{} \" + toString(), \"arg\");",
" LOG.error((Marker) null, \"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.info(\"str \" + toString());",
@@ -279,9 +279,9 @@ final class FormatStringConcatenationCheckTest {
" LOG.info(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.info((Marker) null,\"str \" + toString());",
" LOG.info((Marker) null, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.info((Marker) null,\"{} \" + toString(), \"arg\");",
" LOG.info((Marker) null, \"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.trace(\"str \" + toString());",
@@ -289,9 +289,9 @@ final class FormatStringConcatenationCheckTest {
" LOG.trace(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.trace((Marker) null,\"str \" + toString());",
" LOG.trace((Marker) null, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.trace((Marker) null,\"{} \" + toString(), \"arg\");",
" LOG.trace((Marker) null, \"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.warn(\"str \" + toString());",
@@ -299,9 +299,9 @@ final class FormatStringConcatenationCheckTest {
" LOG.warn(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.warn((Marker) null,\"str \" + toString());",
" LOG.warn((Marker) null, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.warn((Marker) null,\"{} \" + toString(), \"arg\");",
" LOG.warn((Marker) null, \"{} \" + toString(), \"arg\");",
" }",
"}")
.doTest();
@@ -316,7 +316,6 @@ final class FormatStringConcatenationCheckTest {
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import org.slf4j.Logger;",
"import org.slf4j.LoggerFactory;",
"import org.slf4j.Marker;",
@@ -366,7 +365,6 @@ final class FormatStringConcatenationCheckTest {
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import org.slf4j.Logger;",
"import org.slf4j.LoggerFactory;",
"import org.slf4j.Marker;",
@@ -400,6 +398,7 @@ final class FormatStringConcatenationCheckTest {
" String.format(\"{} \" + toString(), \"arg\");",
" String.format(Locale.ROOT, \"{} \" + toString(), \"arg\");",
" }",
"",
" void slf4j() {",
" LOG.debug(\"str {}\", toString());",
" LOG.debug((Marker) null, \"str {}\", toString());",

View File

@@ -6,11 +6,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IdentityConversionCheckTest {
final class IdentityConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IdentityConversionCheck.class, getClass());
CompilationTestHelper.newInstance(IdentityConversion.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IdentityConversionCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass());
@Test
void identification() {
@@ -121,8 +121,9 @@ final class IdentityConversionCheckTest {
" ImmutableBiMap<Object, Object> o1 = ImmutableBiMap.copyOf(ImmutableBiMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableList<Object> o2 = ImmutableList.copyOf(ImmutableList.of());",
" // BUG: Diagnostic contains:",
" ImmutableListMultimap<Object, Object> o3 = ImmutableListMultimap.copyOf(ImmutableListMultimap.of());",
" ImmutableListMultimap<Object, Object> o3 =",
" // BUG: Diagnostic contains:",
" ImmutableListMultimap.copyOf(ImmutableListMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMap<Object, Object> o4 = ImmutableMap.copyOf(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
@@ -135,8 +136,9 @@ final class IdentityConversionCheckTest {
" ImmutableRangeSet<String> o8 = ImmutableRangeSet.copyOf(ImmutableRangeSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSet<Object> o9 = ImmutableSet.copyOf(ImmutableSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSetMultimap<Object, Object> o10 = ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of());",
" ImmutableSetMultimap<Object, Object> o10 =",
" // BUG: Diagnostic contains:",
" ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableTable<Object, Object, Object> o11 = ImmutableTable.copyOf(ImmutableTable.of());",
"",
@@ -171,8 +173,8 @@ final class IdentityConversionCheckTest {
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.Collection;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"import org.reactivestreams.Publisher;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
@@ -204,8 +206,7 @@ final class IdentityConversionCheckTest {
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.copyOf(ImmutableSet.of());",
"",
" when(\"foo\".contains(\"f\"))",
" .thenAnswer(inv-> ImmutableSet.copyOf(ImmutableList.of(1)));",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"",
" void bar(Publisher<Integer> publisher) {}",
@@ -217,8 +218,8 @@ final class IdentityConversionCheckTest {
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.Collection;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"import org.reactivestreams.Publisher;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
@@ -250,8 +251,7 @@ final class IdentityConversionCheckTest {
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.of();",
"",
" when(\"foo\".contains(\"f\"))",
" .thenAnswer(inv-> ImmutableSet.copyOf(ImmutableList.of(1)));",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"",
" void bar(Publisher<Integer> publisher) {}",
@@ -269,9 +269,6 @@ final class IdentityConversionCheckTest {
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
@@ -288,9 +285,6 @@ final class IdentityConversionCheckTest {
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",

View File

@@ -0,0 +1,182 @@
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 ImmutablesSortedSetComparatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.collect.ContiguousSet;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSortedSet;",
"import java.util.NavigableSet;",
"import java.util.Set;",
"import java.util.SortedSet;",
"import java.util.TreeSet;",
"import org.immutables.value.Value;",
"",
"interface A {",
" @Value.Immutable",
" interface ImmutableInterface {",
" Set<String> set();",
"",
" // BUG: Diagnostic contains:",
" SortedSet<String> sortedSet();",
"",
" @Value.NaturalOrder",
" SortedSet<String> sortedSet2();",
" }",
"",
" @Value.Modifiable",
" interface ModifiableInterfaceWithDefaults {",
" @Value.Default",
" default Set<Integer> set() {",
" return new TreeSet<>();",
" }",
"",
" @Value.Default",
" // BUG: Diagnostic contains:",
" default NavigableSet<Integer> navigableSet() {",
" return new TreeSet<>();",
" }",
"",
" @Value.Default",
" @Value.ReverseOrder",
" default NavigableSet<Integer> navigableSet2() {",
" return new TreeSet<>();",
" }",
"",
" default NavigableSet<Integer> nonPropertyNavigableSet() {",
" return new TreeSet<>();",
" }",
" }",
"",
" interface NonImmutablesInterface {",
" SortedSet<String> sortedSet();",
" }",
"",
" @Value.Immutable",
" abstract class AbstractImmutableWithDefaults {",
" @Value.Default",
" ImmutableSet<Integer> immutableSet() {",
" return ImmutableSet.of();",
" }",
"",
" @Value.Default",
" // BUG: Diagnostic contains:",
" ImmutableSortedSet<String> immutableSortedSet() {",
" return ImmutableSortedSet.of();",
" }",
"",
" @Value.Default",
" @Value.NaturalOrder",
" ImmutableSortedSet<String> immutableSortedSet2() {",
" return ImmutableSortedSet.of();",
" }",
"",
" ImmutableSortedSet<String> nonPropertyImmutableSortedSet() {",
" return ImmutableSortedSet.of();",
" }",
" }",
"",
" @Value.Modifiable",
" abstract class AbstractModifiable {",
" abstract ImmutableSet<Integer> immutableSet();",
"",
" // BUG: Diagnostic contains:",
" abstract ContiguousSet<Integer> contiguousSet();",
"",
" @Value.ReverseOrder",
" abstract ContiguousSet<Integer> contiguousSet2();",
" }",
"",
" abstract class AbstractNonImmutables {",
" abstract SortedSet<Integer> sortedSet();",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import com.google.common.collect.ImmutableSortedSet;",
"import java.util.SortedSet;",
"import org.immutables.value.Value;",
"",
"@Value.Immutable",
"abstract class A {",
" abstract ImmutableSortedSet<String> sortedSet();",
"",
" @Value.Modifiable",
" interface B {",
" SortedSet<String> sortedSet();",
" }",
"}")
.addOutputLines(
"A.java",
"import com.google.common.collect.ImmutableSortedSet;",
"import java.util.SortedSet;",
"import org.immutables.value.Value;",
"",
"@Value.Immutable",
"abstract class A {",
" @Value.NaturalOrder",
" abstract ImmutableSortedSet<String> sortedSet();",
"",
" @Value.Modifiable",
" interface B {",
" @Value.NaturalOrder",
" SortedSet<String> sortedSet();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementWithImportClash() {
refactoringTestHelper
.addInputLines(
"MySpringService.java",
"import com.google.common.collect.ImmutableSortedSet;",
"import org.springframework.beans.factory.annotation.Value;",
"",
"class MySpringService {",
" MySpringService(@Value(\"${someProperty}\") String prop) {}",
" ;",
"",
" @org.immutables.value.Value.Immutable",
" interface A {",
" ImmutableSortedSet<String> sortedSet();",
" }",
"}")
.addOutputLines(
"MySpringService.java",
"import com.google.common.collect.ImmutableSortedSet;",
"import org.springframework.beans.factory.annotation.Value;",
"",
"class MySpringService {",
" MySpringService(@Value(\"${someProperty}\") String prop) {}",
" ;",
"",
" @org.immutables.value.Value.Immutable",
" interface A {",
" @org.immutables.value.Value.NaturalOrder",
" ImmutableSortedSet<String> sortedSet();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,224 +0,0 @@
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 JUnitMethodDeclarationCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitMethodDeclarationCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclarationCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" @BeforeAll void setUp1() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll public void setUp2() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll protected void setUp3() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll private void setUp4() {}",
"",
" @BeforeEach void setup5() {}",
" // BUG: Diagnostic contains:",
" @BeforeEach public void setUp6() {}",
" // BUG: Diagnostic contains:",
" @BeforeEach protected void setUp7() {}",
" // BUG: Diagnostic contains:",
" @BeforeEach private void setUp8() {}",
"",
" @AfterEach void tearDown1() {}",
" // BUG: Diagnostic contains:",
" @AfterEach public void tearDown2() {}",
" // BUG: Diagnostic contains:",
" @AfterEach protected void tearDown3() {}",
" // BUG: Diagnostic contains:",
" @AfterEach private void tearDown4() {}",
"",
" @AfterAll void tearDown5() {}",
" // BUG: Diagnostic contains:",
" @AfterAll public void tearDown6() {}",
" // BUG: Diagnostic contains:",
" @AfterAll protected void tearDown7() {}",
" // BUG: Diagnostic contains:",
" @AfterAll private void tearDown8() {}",
"",
" @Test void method1() {}",
" // BUG: Diagnostic contains:",
" @Test void testMethod2() {}",
" // BUG: Diagnostic contains:",
" @Test public void method3() {}",
" // BUG: Diagnostic contains:",
" @Test protected void method4() {}",
" // BUG: Diagnostic contains:",
" @Test private void method5() {}",
"",
" @ParameterizedTest void method6() {}",
" // BUG: Diagnostic contains:",
" @ParameterizedTest void testMethod7() {}",
" // BUG: Diagnostic contains:",
" @ParameterizedTest public void method8() {}",
" // BUG: Diagnostic contains:",
" @ParameterizedTest protected void method9() {}",
" // BUG: Diagnostic contains:",
" @ParameterizedTest private void method10() {}",
"",
" @BeforeEach @BeforeAll @AfterEach @AfterAll void testNonTestMethod1() {}",
" public void testNonTestMethod2() {}",
" protected void testNonTestMethod3() {}",
" private void testNonTestMethod4() {}",
" @Test void test5() {}",
"",
" // BUG: Diagnostic contains: (but note that a method named `overload` already exists in this class)",
" @Test void testOverload() {}",
" void overload() {}",
" // BUG: Diagnostic contains: (but note that `arguments` is already statically imported)",
" @Test void testArguments() {}",
" // BUG: Diagnostic contains: (but note that `public` is a reserved keyword)",
" @Test void testPublic() {}",
"}")
.addSourceLines(
"B.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class B extends A {",
" @Override @BeforeAll void setUp1() {}",
" @Override @BeforeAll public void setUp2() {}",
" @Override @BeforeAll protected void setUp3() {}",
"",
" @Override @BeforeEach void setup5() {}",
" @Override @BeforeEach public void setUp6() {}",
" @Override @BeforeEach protected void setUp7() {}",
"",
" @Override @AfterEach void tearDown1() {}",
" @Override @AfterEach public void tearDown2() {}",
" @Override @AfterEach protected void tearDown3() {}",
"",
" @Override @AfterAll void tearDown5() {}",
" @Override @AfterAll public void tearDown6() {}",
" @Override @AfterAll protected void tearDown7() {}",
"",
" @Override @Test void method1() {}",
" @Override @Test void testMethod2() {}",
" @Override @Test public void method3() {}",
" @Override @Test protected void method4() {}",
"",
" @Override @ParameterizedTest void method6() {}",
" @Override @ParameterizedTest void testMethod7() {}",
" @Override @ParameterizedTest public void method8() {}",
" @Override @ParameterizedTest protected void method9() {}",
"",
" @Override @BeforeEach @BeforeAll @AfterEach @AfterAll void testNonTestMethod1() {}",
" @Override public void testNonTestMethod2() {}",
" @Override protected void testNonTestMethod3() {}",
" @Override @Test void test5() {}",
"",
" @Override @Test void testOverload() {}",
" @Override void overload() {}",
" @Override @Test void testArguments() {}",
" @Override @Test void testPublic() {}",
"}")
.addSourceLines(
"C.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.Test;",
"",
"abstract class C {",
" @BeforeAll public void setUp() {}",
" @Test void testMethod1() {}",
"",
" // BUG: Diagnostic contains:",
" @AfterAll private void tearDown() {}",
" // BUG: Diagnostic contains:",
" @Test final void testMethod2() {}",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" @BeforeAll public void setUp1() {}",
" @BeforeEach protected void setUp2() {}",
" @AfterEach private void setUp3() {}",
" @AfterAll private void setUp4() {}",
"",
" @Test void testFoo() {}",
" @ParameterizedTest void testBar() {}",
"",
" @Test public void baz() {}",
" @RepeatedTest(2) private void qux() {}",
" @ParameterizedTest protected void quux() {}",
"",
" @Test public void testOverload() {}",
" void overload() {}",
" @Test protected void testArguments() {}",
" @Test private void testClass() {}",
"}")
.addOutputLines(
"out/A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" @BeforeAll void setUp1() {}",
" @BeforeEach void setUp2() {}",
" @AfterEach void setUp3() {}",
" @AfterAll void setUp4() {}",
"",
" @Test void foo() {}",
" @ParameterizedTest void bar() {}",
"",
" @Test void baz() {}",
" @RepeatedTest(2) void qux() {}",
" @ParameterizedTest void quux() {}",
"",
" @Test void testOverload() {}",
" void overload() {}",
" @Test void testArguments() {}",
" @Test void testClass() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,422 @@
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 JUnitMethodDeclarationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" {",
" arguments();",
" }",
"",
" @BeforeAll",
" void setUp1() {}",
"",
" @BeforeAll",
" // BUG: Diagnostic contains:",
" public void setUp2() {}",
"",
" @BeforeAll",
" // BUG: Diagnostic contains:",
" protected void setUp3() {}",
"",
" @BeforeAll",
" // BUG: Diagnostic contains:",
" private void setUp4() {}",
"",
" @BeforeEach",
" void setup5() {}",
"",
" @BeforeEach",
" // BUG: Diagnostic contains:",
" public void setUp6() {}",
"",
" @BeforeEach",
" // BUG: Diagnostic contains:",
" protected void setUp7() {}",
"",
" @BeforeEach",
" // BUG: Diagnostic contains:",
" private void setUp8() {}",
"",
" @AfterEach",
" void tearDown1() {}",
"",
" @AfterEach",
" // BUG: Diagnostic contains:",
" public void tearDown2() {}",
"",
" @AfterEach",
" // BUG: Diagnostic contains:",
" protected void tearDown3() {}",
"",
" @AfterEach",
" // BUG: Diagnostic contains:",
" private void tearDown4() {}",
"",
" @AfterAll",
" void tearDown5() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains:",
" public void tearDown6() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains:",
" protected void tearDown7() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains:",
" private void tearDown8() {}",
"",
" @Test",
" void method1() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void testMethod2() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" public void method3() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" protected void method4() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" private void method5() {}",
"",
" @ParameterizedTest",
" void method6() {}",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" void testMethod7() {}",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" public void method8() {}",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" protected void method9() {}",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" private void method10() {}",
"",
" @BeforeEach",
" @BeforeAll",
" @AfterEach",
" @AfterAll",
" void testNonTestMethod1() {}",
"",
" public void testNonTestMethod2() {}",
"",
" protected void testNonTestMethod3() {}",
"",
" private void testNonTestMethod4() {}",
"",
" @Test",
" void test5() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that a method named `overload` already exists in this",
" // class)",
" void testOverload() {}",
"",
" void overload() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that `arguments` is already statically imported)",
" void testArguments() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that `public` is a reserved keyword)",
" void testPublic() {}",
"}")
.addSourceLines(
"B.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class B extends A {",
" @Override",
" @BeforeAll",
" void setUp1() {}",
"",
" @Override",
" @BeforeAll",
" public void setUp2() {}",
"",
" @Override",
" @BeforeAll",
" protected void setUp3() {}",
"",
" @Override",
" @BeforeEach",
" void setup5() {}",
"",
" @Override",
" @BeforeEach",
" public void setUp6() {}",
"",
" @Override",
" @BeforeEach",
" protected void setUp7() {}",
"",
" @Override",
" @AfterEach",
" void tearDown1() {}",
"",
" @Override",
" @AfterEach",
" public void tearDown2() {}",
"",
" @Override",
" @AfterEach",
" protected void tearDown3() {}",
"",
" @Override",
" @AfterAll",
" void tearDown5() {}",
"",
" @Override",
" @AfterAll",
" public void tearDown6() {}",
"",
" @Override",
" @AfterAll",
" protected void tearDown7() {}",
"",
" @Override",
" @Test",
" void method1() {}",
"",
" @Override",
" @Test",
" void testMethod2() {}",
"",
" @Override",
" @Test",
" public void method3() {}",
"",
" @Override",
" @Test",
" protected void method4() {}",
"",
" @Override",
" @ParameterizedTest",
" void method6() {}",
"",
" @Override",
" @ParameterizedTest",
" void testMethod7() {}",
"",
" @Override",
" @ParameterizedTest",
" public void method8() {}",
"",
" @Override",
" @ParameterizedTest",
" protected void method9() {}",
"",
" @Override",
" @BeforeEach",
" @BeforeAll",
" @AfterEach",
" @AfterAll",
" void testNonTestMethod1() {}",
"",
" @Override",
" public void testNonTestMethod2() {}",
"",
" @Override",
" protected void testNonTestMethod3() {}",
"",
" @Override",
" @Test",
" void test5() {}",
"",
" @Override",
" @Test",
" void testOverload() {}",
"",
" @Override",
" void overload() {}",
"",
" @Override",
" @Test",
" void testArguments() {}",
"",
" @Override",
" @Test",
" void testPublic() {}",
"}")
.addSourceLines(
"C.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.Test;",
"",
"abstract class C {",
" @BeforeAll",
" public void setUp() {}",
"",
" @Test",
" void testMethod1() {}",
"",
" @AfterAll",
" // BUG: Diagnostic contains:",
" private void tearDown() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" final void testMethod2() {}",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" {",
" arguments();",
" }",
"",
" @BeforeAll",
" public void setUp1() {}",
"",
" @BeforeEach",
" protected void setUp2() {}",
"",
" @AfterEach",
" private void setUp3() {}",
"",
" @AfterAll",
" private void setUp4() {}",
"",
" @Test",
" void testFoo() {}",
"",
" @ParameterizedTest",
" void testBar() {}",
"",
" @Test",
" public void baz() {}",
"",
" @RepeatedTest(2)",
" private void qux() {}",
"",
" @ParameterizedTest",
" protected void quux() {}",
"",
" @Test",
" public void testOverload() {}",
"",
" void overload() {}",
"",
" @Test",
" protected void testArguments() {}",
"",
" @Test",
" private void testClass() {}",
"}")
.addOutputLines(
"out/A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"import org.junit.jupiter.api.RepeatedTest;",
"import org.junit.jupiter.api.Test;",
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" {",
" arguments();",
" }",
"",
" @BeforeAll",
" void setUp1() {}",
"",
" @BeforeEach",
" void setUp2() {}",
"",
" @AfterEach",
" void setUp3() {}",
"",
" @AfterAll",
" void setUp4() {}",
"",
" @Test",
" void foo() {}",
"",
" @ParameterizedTest",
" void bar() {}",
"",
" @Test",
" void baz() {}",
"",
" @RepeatedTest(2)",
" void qux() {}",
"",
" @ParameterizedTest",
" void quux() {}",
"",
" @Test",
" void testOverload() {}",
"",
" void overload() {}",
"",
" @Test",
" void testArguments() {}",
"",
" @Test",
" void testClass() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,199 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationAttributeListingCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(
LexicographicalAnnotationAttributeListingCheck.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(
LexicographicalAnnotationAttributeListingCheck.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(
LexicographicalAnnotationAttributeListingCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.math.RoundingMode.UP;",
"import static java.math.RoundingMode.DOWN;",
"",
"import com.fasterxml.jackson.annotation.JsonPropertyOrder;",
"import io.swagger.annotations.ApiImplicitParam;",
"import io.swagger.annotations.ApiImplicitParams;",
"import io.swagger.v3.oas.annotations.Parameter;",
"import io.swagger.v3.oas.annotations.Parameters;",
"import java.math.RoundingMode;",
"import javax.xml.bind.annotation.XmlType;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" int[] ints() default {};",
" Class<?>[] cls() default {};",
" RoundingMode[] enums() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({}) A noString();",
" @Foo({\"a\"}) A oneString();",
" @Foo({\"a\", \"b\"}) A sortedStrings();",
" // BUG: Diagnostic contains:",
" @Foo({\"b\", \"a\"}) A unsortedString();",
" @Foo({\"ab\", \"Ac\"}) A sortedStringCaseInsensitive();",
" // BUG: Diagnostic contains:",
" @Foo({\"ac\", \"Ab\"}) A unsortedStringCaseInsensitive();",
" @Foo({\"A\", \"a\"}) A sortedStringCaseInsensitiveWithTotalOrderFallback();",
" // BUG: Diagnostic contains:",
" @Foo({\"a\", \"A\"}) A unsortedStringCaseInsensitiveWithTotalOrderFallback();",
"",
" @Foo(ints = {}) A noInts();",
" @Foo(ints = {0}) A oneInt();",
" @Foo(ints = {0, 1}) A sortedInts();",
" @Foo(ints = {1, 0}) A unsortedInts();",
"",
" @Foo(cls = {}) A noClasses();",
" @Foo(cls = {int.class}) A oneClass();",
" @Foo(cls = {int.class, long.class}) A sortedClasses();",
" // BUG: Diagnostic contains:",
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
"",
" @Foo(enums = {}) A noEnums();",
" @Foo(enums = {DOWN}) A oneEnum();",
" @Foo(enums = {DOWN, UP}) A sortedEnums();",
" // BUG: Diagnostic contains:",
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
"",
" @Foo(anns = {}) A noAnns();",
" @Foo(anns = {@Bar(\"a\")}) A oneAnn();",
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")}) A sortedAnns();",
" // BUG: Diagnostic contains:",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
" // BUG: Diagnostic contains:",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
"",
" @Foo({\"a=foo\", \"a.b=bar\", \"a.c=baz\"}) A hierarchicallySorted();",
" // BUG: Diagnostic contains:",
" @Foo({\"a.b=bar\", \"a.c=baz\", \"a=foo\"}) A hierarchicallyUnsorted();",
"",
" @JsonPropertyOrder({\"field2\", \"field1\"}) A dto();",
" @ApiImplicitParams({@ApiImplicitParam(\"p2\"), @ApiImplicitParam(\"p1\")}) A firstEndpoint();",
" @Parameters({@Parameter(name = \"p2\"), @Parameter(name = \"p1\")}) A secondEndpoint();",
"",
" @XmlType(propOrder = {\"field2\", \"field1\"})",
" class Dummy {}",
"}")
.doTest();
}
// XXX: Note that in the output below in one instance redundant `value =` assignments are
// introduced. Avoiding that might make the code too complex. Instead, users can have the
// `CanonicalAnnotationSyntaxCheck` correct the situation in a subsequent run.
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static java.math.RoundingMode.UP;",
"import static java.math.RoundingMode.DOWN;",
"",
"import java.math.RoundingMode;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" Class<?>[] cls() default {};",
" RoundingMode[] enums() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({\"b\", \"a\"}) A unsortedString();",
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
"}")
.addOutputLines(
"out/A.java",
"import static java.math.RoundingMode.UP;",
"import static java.math.RoundingMode.DOWN;",
"",
"import java.math.RoundingMode;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" Class<?>[] cls() default {};",
" RoundingMode[] enums() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({\"a\", \"b\"}) A unsortedString();",
" @Foo(cls = {int.class, long.class}) A unsortedClasses();",
" @Foo(enums = {DOWN, UP}) A unsortedEnums();",
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")}) A unsortedAnns();",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"a\", \"b\"})}) A unsortedInnderAnns();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void filtering() {
/* Some violations are not flagged because they are not in- or excluded. */
restrictedCompilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" String[] value2() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" String[] value2() default {};",
" }",
"",
" @interface Baz {",
" String[] value() default {};",
" String[] value2() default {};",
" }",
"",
" // BUG: Diagnostic contains:",
" @Foo({\"b\", \"a\"}) A fooValue();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {\"b\", \"a\"}) A fooValue2();",
" @Bar({\"b\", \"a\"}) A barValue();",
" // BUG: Diagnostic contains:",
" @Bar(value2 = {\"b\", \"a\"}) A barValue2();",
" @Baz({\"b\", \"a\"}) A bazValue();",
" @Baz(value2 = {\"b\", \"a\"}) A bazValue2();",
"}")
.doTest();
}
}

View File

@@ -0,0 +1,294 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
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
.addSourceLines(
"A.java",
"import static java.math.RoundingMode.DOWN;",
"import static java.math.RoundingMode.UP;",
"",
"import com.fasterxml.jackson.annotation.JsonPropertyOrder;",
"import io.swagger.annotations.ApiImplicitParam;",
"import io.swagger.annotations.ApiImplicitParams;",
"import io.swagger.v3.oas.annotations.Parameter;",
"import io.swagger.v3.oas.annotations.Parameters;",
"import java.math.RoundingMode;",
"import javax.xml.bind.annotation.XmlType;",
"import org.springframework.context.annotation.PropertySource;",
"import org.springframework.test.context.TestPropertySource;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] ints() default {};",
"",
" Class<?>[] cls() default {};",
"",
" RoundingMode[] enums() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({})",
" A noString();",
"",
" @Foo({\"a\"})",
" A oneString();",
"",
" @Foo({\"a\", \"b\"})",
" A sortedStrings();",
" // BUG: Diagnostic contains:",
" @Foo({\"b\", \"a\"})",
" A unsortedString();",
"",
" @Foo({\"ab\", \"Ac\"})",
" A sortedStringCaseInsensitive();",
" // BUG: Diagnostic contains:",
" @Foo({\"ac\", \"Ab\"})",
" A unsortedStringCaseInsensitive();",
"",
" @Foo({\"A\", \"a\"})",
" A sortedStringCaseInsensitiveWithTotalOrderFallback();",
" // BUG: Diagnostic contains:",
" @Foo({\"a\", \"A\"})",
" A unsortedStringCaseInsensitiveWithTotalOrderFallback();",
"",
" @Foo(ints = {})",
" A noInts();",
"",
" @Foo(ints = {0})",
" A oneInt();",
"",
" @Foo(ints = {0, 1})",
" A sortedInts();",
"",
" @Foo(ints = {1, 0})",
" A unsortedInts();",
"",
" @Foo(cls = {})",
" A noClasses();",
"",
" @Foo(cls = {int.class})",
" A oneClass();",
"",
" @Foo(cls = {int.class, long.class})",
" A sortedClasses();",
" // BUG: Diagnostic contains:",
" @Foo(cls = {long.class, int.class})",
" A unsortedClasses();",
"",
" @Foo(enums = {})",
" A noEnums();",
"",
" @Foo(enums = {DOWN})",
" A oneEnum();",
"",
" @Foo(enums = {DOWN, UP})",
" A sortedEnums();",
" // BUG: Diagnostic contains:",
" @Foo(enums = {UP, DOWN})",
" A unsortedEnums();",
"",
" @Foo(anns = {})",
" A noAnns();",
"",
" @Foo(anns = {@Bar(\"a\")})",
" A oneAnn();",
"",
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")})",
" A sortedAnns();",
" // BUG: Diagnostic contains:",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" A unsortedAnns();",
" // BUG: Diagnostic contains:",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})})",
" A unsortedInnderAnns();",
"",
" @Foo({\"a=foo\", \"a.b=bar\", \"a.c=baz\"})",
" A hierarchicallySorted();",
" // BUG: Diagnostic contains:",
" @Foo({\"a.b=bar\", \"a.c=baz\", \"a=foo\"})",
" A hierarchicallyUnsorted();",
"",
" @JsonPropertyOrder({\"field2\", \"field1\"})",
" A dto();",
"",
" @ApiImplicitParams({@ApiImplicitParam(\"p2\"), @ApiImplicitParam(\"p1\")})",
" A firstEndpoint();",
"",
" @Parameters({@Parameter(name = \"p2\"), @Parameter(name = \"p1\")})",
" A secondEndpoint();",
"",
" @XmlType(propOrder = {\"field2\", \"field1\"})",
" class XmlTypeDummy {}",
"",
" @PropertySource({\"field2\", \"field1\"})",
" class PropertySourceDummy {}",
"",
" @TestPropertySource(locations = {\"field2\", \"field1\"})",
" class FirstTestPropertySourceDummy {}",
"",
" @TestPropertySource({\"field2\", \"field1\"})",
" class SecondTestPropertySourceDummy {}",
"}")
.doTest();
}
// XXX: Note that in the output below in one instance redundant `value =` assignments are
// introduced. Avoiding that might make the code too complex. Instead, users can have the
// `CanonicalAnnotationSyntax` checker correct the situation in a subsequent run.
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static java.math.RoundingMode.DOWN;",
"import static java.math.RoundingMode.UP;",
"",
"import java.math.RoundingMode;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" Class<?>[] cls() default {};",
"",
" RoundingMode[] enums() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({\"b\", \"a\"})",
" A unsortedString();",
"",
" @Foo(cls = {long.class, int.class})",
" A unsortedClasses();",
"",
" @Foo(enums = {UP, DOWN})",
" A unsortedEnums();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" A unsortedAnns();",
"",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})})",
" A unsortedInnderAnns();",
"}")
.addOutputLines(
"out/A.java",
"import static java.math.RoundingMode.DOWN;",
"import static java.math.RoundingMode.UP;",
"",
"import java.math.RoundingMode;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" Class<?>[] cls() default {};",
"",
" RoundingMode[] enums() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @Foo({\"a\", \"b\"})",
" A unsortedString();",
"",
" @Foo(cls = {int.class, long.class})",
" A unsortedClasses();",
"",
" @Foo(enums = {DOWN, UP})",
" A unsortedEnums();",
"",
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")})",
" A unsortedAnns();",
"",
" @Foo(anns = {@Bar(\"a\"), @Bar({\"a\", \"b\"})})",
" A unsortedInnderAnns();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void filtering() {
/* Some violations are not flagged because they are not in- or excluded. */
restrictedCompilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" String[] value2() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
"",
" String[] value2() default {};",
" }",
"",
" @interface Baz {",
" String[] value() default {};",
"",
" String[] value2() default {};",
" }",
"",
" // BUG: Diagnostic contains:",
" @Foo({\"b\", \"a\"})",
" A fooValue();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {\"b\", \"a\"})",
" A fooValue2();",
"",
" @Bar({\"b\", \"a\"})",
" A barValue();",
" // BUG: Diagnostic contains:",
" @Bar(value2 = {\"b\", \"a\"})",
" A barValue2();",
"",
" @Baz({\"b\", \"a\"})",
" A bazValue();",
"",
" @Baz(value2 = {\"b\", \"a\"})",
" A bazValue2();",
"}")
.doTest();
}
}

View File

@@ -1,148 +0,0 @@
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 LexicographicalAnnotationListingCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationListingCheck.class, getClass())
.expectErrorMessage(
"X", containsPattern("Sort annotations lexicographically where possible"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationListingCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.lang.annotation.Repeatable;",
"",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
" int[] ints() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
"",
" // BUG: Diagnostic matches: X",
" @Foo @Bar A unsortedSimpleCase();",
" // BUG: Diagnostic matches: X",
" @Foo() @Bar() A unsortedWithParens();",
" @Foo() A onlyOneAnnotation();",
" @Bar @Foo() A sortedAnnotationsOneWithParens();",
"",
" // BUG: Diagnostic matches: X",
" @Foo @Baz @Bar A threeUnsortedAnnotationsSameInitialLetter();",
" // BUG: Diagnostic matches: X",
" @Bar @Foo() @Baz A firstOrderedWithTwoUnsortedAnnotations();",
" @Bar @Baz @Foo() A threeSortedAnnotations();",
"",
" // BUG: Diagnostic matches: X",
" @Foo({\"b\"}) @Bar({\"a\"}) A unsortedWithStringAttributes();",
" // BUG: Diagnostic matches: X",
" @Baz(str = {\"a\", \"b\"}) @Foo(ints = {1, 0}) @Bar A unsortedWithAttributes();",
" // BUG: Diagnostic matches: X",
" @Bar @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Baz A unsortedWithNestedBar();",
" @Bar @Baz @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A sortedWithNestedBar();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Foo(ints = {1, 2}) @Foo({\"b\"}) A sortedRepeatableAnnotation();",
" // BUG: Diagnostic matches: X",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Bar @Foo(ints = {1, 2}) A unsortedRepeatableAnnotation();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import java.lang.annotation.Repeatable;",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
" int[] ints() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
"",
" @Bar A singleAnnotation();",
" @Bar @Foo A sortedAnnotations();",
" @Foo @Bar A unsortedAnnotations();",
" @Foo() @Baz() @Bar A unsortedAnnotationsWithSomeParens();",
"",
" @Bar @Baz(str = {\"a\", \"b\"}) @Foo() A unsortedAnnotationsOneContainingAttributes();",
" @Baz(str = {\"a\", \"b\"}) @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Bar({\"b\"}) A unsortedAnnotationsWithAttributes();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Foo(ints = {1, 2}) @Foo({\"b\"}) A sortedRepeatableAnnotation();",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Bar @Foo(ints = {1, 2}) A unsortedRepeatableAnnotation();",
"",
"}")
.addOutputLines(
"out/A.java",
"import java.lang.annotation.Repeatable;",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
" int[] ints() default {};",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
" @Bar A singleAnnotation();",
" @Bar @Foo A sortedAnnotations();",
" @Bar @Foo A unsortedAnnotations();",
" @Bar @Baz() @Foo() A unsortedAnnotationsWithSomeParens();",
"",
" @Bar @Baz(str = {\"a\", \"b\"}) @Foo() A unsortedAnnotationsOneContainingAttributes();",
" @Bar({\"b\"}) @Baz(str = {\"a\", \"b\"}) @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnnotationsWithAttributes();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Foo(ints = {1, 2}) @Foo({\"b\"}) A sortedRepeatableAnnotation();",
" @Bar @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) @Foo(ints = {1, 2}) A unsortedRepeatableAnnotation();",
"",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,242 @@
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 LexicographicalAnnotationListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.expectErrorMessage(
"X", containsPattern("Sort annotations lexicographically where possible"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationListing.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.lang.annotation.Repeatable;",
"",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] ints() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
"",
" // BUG: Diagnostic matches: X",
" @Foo",
" @Bar",
" A unsortedSimpleCase();",
" // BUG: Diagnostic matches: X",
" @Foo()",
" @Bar()",
" A unsortedWithParens();",
"",
" @Foo()",
" A onlyOneAnnotation();",
"",
" @Bar",
" @Foo()",
" A sortedAnnotationsOneWithParens();",
"",
" // BUG: Diagnostic matches: X",
" @Foo",
" @Baz",
" @Bar",
" A threeUnsortedAnnotationsSameInitialLetter();",
" // BUG: Diagnostic matches: X",
" @Bar",
" @Foo()",
" @Baz",
" A firstOrderedWithTwoUnsortedAnnotations();",
"",
" @Bar",
" @Baz",
" @Foo()",
" A threeSortedAnnotations();",
"",
" // BUG: Diagnostic matches: X",
" @Foo({\"b\"})",
" @Bar({\"a\"})",
" A unsortedWithStringAttributes();",
" // BUG: Diagnostic matches: X",
" @Baz(str = {\"a\", \"b\"})",
" @Foo(ints = {1, 0})",
" @Bar",
" A unsortedWithAttributes();",
" // BUG: Diagnostic matches: X",
" @Bar",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Baz",
" A unsortedWithNestedBar();",
"",
" @Bar",
" @Baz",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" A sortedWithNestedBar();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Foo(ints = {1, 2})",
" @Foo({\"b\"})",
" A sortedRepeatableAnnotation();",
" // BUG: Diagnostic matches: X",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Bar",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import java.lang.annotation.Repeatable;",
"",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] ints() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
"",
" @Bar",
" A singleAnnotation();",
"",
" @Bar",
" @Foo",
" A sortedAnnotations();",
"",
" @Foo",
" @Bar",
" A unsortedAnnotations();",
"",
" @Foo()",
" @Baz()",
" @Bar",
" A unsortedAnnotationsWithSomeParens();",
"",
" @Bar",
" @Baz(str = {\"a\", \"b\"})",
" @Foo()",
" A unsortedAnnotationsOneContainingAttributes();",
"",
" @Baz(str = {\"a\", \"b\"})",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Bar({\"b\"})",
" A unsortedAnnotationsWithAttributes();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Foo(ints = {1, 2})",
" @Foo({\"b\"})",
" A sortedRepeatableAnnotation();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Bar",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"}")
.addOutputLines(
"out/A.java",
"import java.lang.annotation.Repeatable;",
"",
"interface A {",
" @Repeatable(Foos.class)",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] ints() default {};",
"",
" Bar[] anns() default {};",
" }",
"",
" @interface Bar {",
" String[] value() default {};",
" }",
"",
" @interface Baz {",
" String[] str() default {};",
" }",
"",
" @interface Foos {",
" Foo[] value();",
" }",
"",
" @Bar",
" A singleAnnotation();",
"",
" @Bar",
" @Foo",
" A sortedAnnotations();",
"",
" @Bar",
" @Foo",
" A unsortedAnnotations();",
"",
" @Bar",
" @Baz()",
" @Foo()",
" A unsortedAnnotationsWithSomeParens();",
"",
" @Bar",
" @Baz(str = {\"a\", \"b\"})",
" @Foo()",
" A unsortedAnnotationsOneContainingAttributes();",
"",
" @Bar({\"b\"})",
" @Baz(str = {\"a\", \"b\"})",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" A unsortedAnnotationsWithAttributes();",
"",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Foo(ints = {1, 2})",
" @Foo({\"b\"})",
" A sortedRepeatableAnnotation();",
"",
" @Bar",
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
" @Foo(ints = {1, 2})",
" A unsortedRepeatableAnnotation();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -5,49 +5,78 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MethodReferenceUsageCheckTest {
final class MethodReferenceUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MethodReferenceUsageCheck.class, getClass());
CompilationTestHelper.newInstance(MethodReferenceUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsageCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsage.class, getClass());
// XXX: Disable the `replacement` test and verify using PIT that this test covers all
// identification cases.
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.collect.Streams;",
"import java.util.Map;",
"import com.google.errorprone.refaster.annotation.AfterTemplate;",
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
"import java.util.HashMap;",
"import java.util.stream.Stream;",
"import java.util.Map;",
"import java.util.function.IntConsumer;",
"import java.util.function.IntFunction;",
"import java.util.stream.Stream;",
"",
"class A {",
" private final Stream<Integer> s = Stream.of(1);",
" private final Map<Integer, Integer> m = new HashMap<>();",
" private final Runnable thrower = () -> { throw new RuntimeException(); };",
" private final Runnable thrower =",
" () -> {",
" throw new RuntimeException();",
" };",
"",
" void unaryExternalStaticFunctionCalls() {",
" s.forEach(String::valueOf);",
" // BUG: Diagnostic contains:",
" s.forEach(v -> String.valueOf(v));",
" // BUG: Diagnostic contains:",
" s.forEach((v) -> { String.valueOf(v); });",
" // BUG: Diagnostic contains:",
" s.forEach((Integer v) -> { { String.valueOf(v); } });",
" s.forEach(v -> { String.valueOf(v); String.valueOf(v); });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" (v) -> {",
" String.valueOf(v);",
" });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" (Integer v) -> {",
" {",
" String.valueOf(v);",
" }",
" });",
" s.forEach(",
" v -> {",
" String.valueOf(v);",
" String.valueOf(v);",
" });",
"",
" s.map(String::valueOf);",
" // BUG: Diagnostic contains:",
" s.map(v -> String.valueOf(v));",
" // BUG: Diagnostic contains:",
" s.map((v) -> (String.valueOf(v)));",
" // BUG: Diagnostic contains:",
" s.map((Integer v) -> { return String.valueOf(v); });",
" // BUG: Diagnostic contains:",
" s.map((final Integer v) -> { return (String.valueOf(v)); });",
" s.map(v -> { String.valueOf(v); return String.valueOf(v); });",
" s.map(",
" // BUG: Diagnostic contains:",
" (Integer v) -> {",
" return String.valueOf(v);",
" });",
" s.map(",
" // BUG: Diagnostic contains:",
" (final Integer v) -> {",
" return (String.valueOf(v));",
" });",
" s.map(",
" v -> {",
" String.valueOf(v);",
" return String.valueOf(v);",
" });",
"",
" s.findFirst().orElseGet(() -> Integer.valueOf(\"0\"));",
" m.forEach((k, v) -> String.valueOf(v));",
@@ -59,14 +88,34 @@ final class MethodReferenceUsageCheckTest {
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> m.put(k, v));",
" m.forEach((k, v) -> m.put(v, k));",
" // BUG: Diagnostic contains:",
" m.forEach((Integer k, Integer v) -> { m.put(k, v); });",
" m.forEach((k, v) -> { m.put(k, k); });",
" // BUG: Diagnostic contains:",
" m.forEach((final Integer k, final Integer v) -> { { m.put(k, v); } });",
" m.forEach((k, v) -> { { m.put(v, v); } });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (Integer k, Integer v) -> {",
" m.put(k, v);",
" });",
" m.forEach(",
" (k, v) -> {",
" m.put(k, k);",
" });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (final Integer k, final Integer v) -> {",
" {",
" m.put(k, v);",
" }",
" });",
" m.forEach(",
" (k, v) -> {",
" {",
" m.put(v, v);",
" }",
" });",
" m.forEach((k, v) -> new HashMap<Integer, Integer>().put(k, v));",
" m.forEach((k, v) -> { m.put(k, v); m.put(k, v); });",
" m.forEach(",
" (k, v) -> {",
" m.put(k, v);",
" m.put(k, v);",
" });",
"",
" Streams.zip(s, s, m::put);",
" // BUG: Diagnostic contains:",
@@ -75,20 +124,45 @@ final class MethodReferenceUsageCheckTest {
" // BUG: Diagnostic contains:",
" Streams.zip(s, s, (Integer a, Integer b) -> (m.put(a, b)));",
" Streams.zip(s, s, (a, b) -> (m.put(a, a)));",
" // BUG: Diagnostic contains:",
" Streams.zip(s, s, (final Integer a, final Integer b) -> { return m.put(a, b); });",
" Streams.zip(s, s, (a, b) -> { return m.put(b, b); });",
" // BUG: Diagnostic contains:",
" Streams.zip(s, s, (a, b) -> { return (m.put(a, b)); });",
" Streams.zip(s, s, (a, b) -> { return (m.put(b, a)); });",
" Streams.zip(s, s, (a, b) -> { m.put(a, b); return m.put(a, b); });",
" Streams.zip(",
" s,",
" s,",
" // BUG: Diagnostic contains:",
" (final Integer a, final Integer b) -> {",
" return m.put(a, b);",
" });",
" Streams.zip(",
" s,",
" s,",
" (a, b) -> {",
" return m.put(b, b);",
" });",
" Streams.zip(",
" s,",
" s,",
" // BUG: Diagnostic contains:",
" (a, b) -> {",
" return (m.put(a, b));",
" });",
" Streams.zip(",
" s,",
" s,",
" (a, b) -> {",
" return (m.put(b, a));",
" });",
" Streams.zip(",
" s,",
" s,",
" (a, b) -> {",
" m.put(a, b);",
" return m.put(a, b);",
" });",
" }",
"",
" void nullaryExternalInstanceFunctionCalls() {",
" s.map(Integer::doubleValue);",
" // BUG: Diagnostic contains:",
" s.map(i -> i.doubleValue());",
// `s.map(Integer::toString)` is ambiguous
" s.map(i -> i.toString());",
" s.map(i -> s.toString());",
"",
@@ -108,24 +182,36 @@ final class MethodReferenceUsageCheckTest {
" s.forEach(this::ivoid1);",
" // BUG: Diagnostic contains:",
" s.forEach(v -> ivoid1(v));",
" // BUG: Diagnostic contains:",
" s.forEach(v -> { ivoid1(v); });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" v -> {",
" ivoid1(v);",
" });",
" s.forEach(this::iint1);",
" // BUG: Diagnostic contains:",
" s.forEach(v -> iint1(v));",
" // BUG: Diagnostic contains:",
" s.forEach(v -> { iint1(v); });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" v -> {",
" iint1(v);",
" });",
"",
" s.forEach(A::svoid1);",
" // BUG: Diagnostic contains:",
" s.forEach(v -> svoid1(v));",
" // BUG: Diagnostic contains:",
" s.forEach(v -> { svoid1(v); });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" v -> {",
" svoid1(v);",
" });",
" s.forEach(A::sint1);",
" // BUG: Diagnostic contains:",
" s.forEach(v -> sint1(v));",
" // BUG: Diagnostic contains:",
" s.forEach(v -> { sint1(v); });",
" s.forEach(",
" // BUG: Diagnostic contains:",
" v -> {",
" sint1(v);",
" });",
"",
" s.forEach(v -> ivoid2(v, v));",
" s.forEach(v -> iint2(v, v));",
@@ -140,28 +226,55 @@ final class MethodReferenceUsageCheckTest {
" m.forEach(this::ivoid2);",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> ivoid2(k, v));",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> { ivoid2(k, v); });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (k, v) -> {",
" ivoid2(k, v);",
" });",
" m.forEach(this::iint2);",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> iint2(k, v));",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> { iint2(k, v); });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (k, v) -> {",
" iint2(k, v);",
" });",
"",
" m.forEach(A::svoid2);",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> svoid2(k, v));",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> { svoid2(k, v); });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (k, v) -> {",
" svoid2(k, v);",
" });",
" m.forEach(A::sint2);",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> sint2(k, v));",
" // BUG: Diagnostic contains:",
" m.forEach((k, v) -> { sint2(k, v); });",
" m.forEach(",
" // BUG: Diagnostic contains:",
" (k, v) -> {",
" sint2(k, v);",
" });",
" }",
"",
" void functionCallsWhoseReplacementWouldBeAmbiguous() {",
" receiver(i -> { Integer.toString(i); });",
" receiver(",
" i -> {",
" Integer.toString(i);",
" });",
" }",
"",
" @AfterTemplate",
" void refasterBeforeTemplateFunctionCalls() {",
" s.forEach(v -> String.valueOf(v));",
" s.forEach((v) -> { String.valueOf(v); });",
" }",
"",
" @BeforeTemplate",
" void refasterAfterTemplateFunctionCalls() {",
" m.forEach((k, v) -> m.put(k, v));",
" m.forEach((Integer k, Integer v) -> { m.put(k, v); });",
" }",
"",
" void assortedOtherEdgeCases() {",
@@ -175,23 +288,47 @@ final class MethodReferenceUsageCheckTest {
" TernaryOp o7 = (a, b, c) -> b.concat(c);",
" }",
"",
" void receiver(IntFunction<?> op) { }",
" void receiver(IntConsumer op) { }",
" void receiver(IntFunction<?> op) {}",
"",
" void ivoid0() { }",
" void ivoid1(int a) { }",
" void ivoid2(int a, int b) { }",
" int iint0() { return 0; }",
" int iint1(int a) { return 0; }",
" int iint2(int a, int b) { return 0; }",
" void receiver(IntConsumer op) {}",
"",
" static void svoid0() { }",
" static void svoid1(int a) { }",
" static void svoid2(int a, int b) { }",
" static void svoid3(int a, int b, int c) { }",
" static int sint0() { return 0; }",
" static int sint1(int a) { return 0; }",
" static int sint2(int a, int b) { return 0; }",
" void ivoid0() {}",
"",
" void ivoid1(int a) {}",
"",
" void ivoid2(int a, int b) {}",
"",
" int iint0() {",
" return 0;",
" }",
"",
" int iint1(int a) {",
" return 0;",
" }",
"",
" int iint2(int a, int b) {",
" return 0;",
" }",
"",
" static void svoid0() {}",
"",
" static void svoid1(int a) {}",
"",
" static void svoid2(int a, int b) {}",
"",
" static void svoid3(int a, int b, int c) {}",
"",
" static int sint0() {",
" return 0;",
" }",
"",
" static int sint1(int a) {",
" return 0;",
" }",
"",
" static int sint2(int a, int b) {",
" return 0;",
" }",
"",
" interface TernaryOp {",
" String collect(String a, String b, String c);",
@@ -210,9 +347,6 @@ final class MethodReferenceUsageCheckTest {
"import java.util.Collections;",
"import java.util.List;",
"import java.util.Map;",
// Don't import `java.util.Set`; it should be added.
"import java.util.function.IntConsumer;",
"import java.util.function.IntFunction;",
"import java.util.function.IntSupplier;",
"import java.util.function.Supplier;",
"import java.util.stream.Stream;",
@@ -245,12 +379,19 @@ final class MethodReferenceUsageCheckTest {
" Stream.of((Map<?, ?>) null).map(Map::keySet).map(s -> s.size());",
" }",
"",
" @Override int iint0() { return 0; }",
" @Override",
" int iint0() {",
" return 0;",
" }",
" }",
"",
" int iint0() { return 0; }",
" int iint0() {",
" return 0;",
" }",
"",
" static int sint0() { return 0; }",
" static int sint0() {",
" return 0;",
" }",
"}")
.addOutputLines(
"out/A.java",
@@ -260,8 +401,6 @@ final class MethodReferenceUsageCheckTest {
"import java.util.List;",
"import java.util.Map;",
"import java.util.Set;",
"import java.util.function.IntConsumer;",
"import java.util.function.IntFunction;",
"import java.util.function.IntSupplier;",
"import java.util.function.Supplier;",
"import java.util.stream.Stream;",
@@ -294,12 +433,19 @@ final class MethodReferenceUsageCheckTest {
" Stream.of((Map<?, ?>) null).map(Map::keySet).map(Set::size);",
" }",
"",
" @Override int iint0() { return 0; }",
" @Override",
" int iint0() {",
" return 0;",
" }",
" }",
"",
" int iint0() { return 0; }",
" int iint0() {",
" return 0;",
" }",
"",
" static int sint0() { return 0; }",
" static int sint0() {",
" return 0;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -5,9 +5,9 @@ import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MissingRefasterAnnotationCheckTest {
final class MissingRefasterAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MissingRefasterAnnotationCheck.class, getClass())
CompilationTestHelper.newInstance(MissingRefasterAnnotation.class, getClass())
.expectErrorMessage(
"X",
containsPattern(

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoStubbingCheckTest {
final class MockitoStubbingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MockitoStubbingCheck.class, getClass());
CompilationTestHelper.newInstance(MockitoStubbing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbingCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbing.class, getClass());
@Test
void identification() {
@@ -42,8 +42,10 @@ final class MockitoStubbingCheckTest {
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(eq(0), notNull());",
" doAnswer(inv -> null).when(biConsumer).accept(notNull(), eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" // BUG: Diagnostic contains:",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
@@ -71,7 +73,9 @@ final class MockitoStubbingCheckTest {
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")

View File

@@ -0,0 +1,42 @@
package tech.picnic.errorprone.bugpatterns;
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
.addSourceLines(
"A.java",
"import java.util.Optional;",
"import java.util.stream.Stream;",
"",
"class A {",
" void m() {",
" Optional.empty();",
" Optional.of(1);",
" // BUG: Diagnostic contains:",
" Optional.of(Optional.empty());",
" // BUG: Diagnostic contains:",
" Optional.of(Optional.of(1));",
"",
" Optional.ofNullable(null);",
" // BUG: Diagnostic contains:",
" Optional.ofNullable((Optional) null);",
"",
" Optional.of(\"foo\").map(String::length);",
" // BUG: Diagnostic contains:",
" Optional.of(\"foo\").map(Optional::of);",
"",
" Stream.of(\"foo\").findFirst();",
" // BUG: Diagnostic contains:",
" Stream.of(\"foo\").map(Optional::of).findFirst();",
" }",
"}")
.doTest();
}
}

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class PrimitiveComparisonCheckTest {
final class PrimitiveComparisonTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(PrimitiveComparisonCheck.class, getClass());
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparisonCheck.class, getClass());
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.
@@ -99,10 +99,21 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparingDouble(o -> Byte.valueOf((byte) 0));",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private byte toPrimitive(Object o) { return 0; }",
" private Byte toBoxed(Object o) { return 0; }",
" private Function<Object, Byte> toBoxed() { return o -> 0; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private byte toPrimitive(Object o) {",
" return 0;",
" }",
"",
" private Byte toBoxed(Object o) {",
" return 0;",
" }",
"",
" private Function<Object, Byte> toBoxed() {",
" return o -> 0;",
" }",
"}")
.doTest();
}
@@ -191,11 +202,25 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparingDouble(o -> Integer.valueOf(0));",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private int toPrimitive(Object o) { return 0; }",
" private Integer toBoxed(Object o) { return 0; }",
" private Function<Object, Integer> toBoxed() { return o -> 0; }",
" private ToIntFunction<Object> toPrimitive() { return o -> 0; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private int toPrimitive(Object o) {",
" return 0;",
" }",
"",
" private Integer toBoxed(Object o) {",
" return 0;",
" }",
"",
" private Function<Object, Integer> toBoxed() {",
" return o -> 0;",
" }",
"",
" private ToIntFunction<Object> toPrimitive() {",
" return o -> 0;",
" }",
"}")
.doTest();
}
@@ -268,11 +293,25 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparingDouble(o -> Long.valueOf(0));",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private long toPrimitive(Object o) { return 0L; }",
" private Long toBoxed(Object o) { return 0L; }",
" private Function<Object, Long> toBoxed() { return o -> 0L; }",
" private ToLongFunction<Object> toPrimitive() { return o -> 0L; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private long toPrimitive(Object o) {",
" return 0L;",
" }",
"",
" private Long toBoxed(Object o) {",
" return 0L;",
" }",
"",
" private Function<Object, Long> toBoxed() {",
" return o -> 0L;",
" }",
"",
" private ToLongFunction<Object> toPrimitive() {",
" return o -> 0L;",
" }",
"}")
.doTest();
}
@@ -326,10 +365,21 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparingDouble(o -> Float.valueOf(0));",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private float toPrimitive(Object o) { return 0.0f; }",
" private Float toBoxed(Object o) { return 0.0f; }",
" private Function<Object, Float> toBoxed() { return o -> 0.0f; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private float toPrimitive(Object o) {",
" return 0.0f;",
" }",
"",
" private Float toBoxed(Object o) {",
" return 0.0f;",
" }",
"",
" private Function<Object, Float> toBoxed() {",
" return o -> 0.0f;",
" }",
"}")
.doTest();
}
@@ -386,11 +436,25 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparingDouble(o -> Double.valueOf(0));",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private double toPrimitive(Object o) { return 0.0; }",
" private Double toBoxed(Object o) { return 0.0; }",
" private Function<Object, Double> toBoxed() { return o -> 0.0; }",
" private ToDoubleFunction<Object> toPrimitive() { return o -> 0.0; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private double toPrimitive(Object o) {",
" return 0.0;",
" }",
"",
" private Double toBoxed(Object o) {",
" return 0.0;",
" }",
"",
" private Function<Object, Double> toBoxed() {",
" return o -> 0.0;",
" }",
"",
" private ToDoubleFunction<Object> toPrimitive() {",
" return o -> 0.0;",
" }",
"}")
.doTest();
}
@@ -420,8 +484,13 @@ final class PrimitiveComparisonCheckTest {
" cmp().thenComparing(toStr(), cmp());",
" }",
"",
" private Comparator<Object> cmp() { return null; }",
" private Function<Object, String> toStr() { return String::valueOf; }",
" private Comparator<Object> cmp() {",
" return null;",
" }",
"",
" private Function<Object, String> toStr() {",
" return String::valueOf;",
" }",
"}")
.doTest();
}
@@ -692,22 +761,30 @@ final class PrimitiveComparisonCheckTest {
"import java.util.Comparator;",
"",
"interface A extends Comparable<A> {",
" Comparator<A> bCmp = Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp = Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp = Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Short.valueOf((short) 0));",
" Comparator<A> bCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Short.valueOf((short) 0));",
" Comparator<A> iCmp = Comparator.<A, A>comparing(o -> o).thenComparingInt(o -> Integer.valueOf(0));",
" Comparator<A> lCmp = Comparator.<A, A>comparing(o -> o).thenComparingLong(o -> Long.valueOf(0));",
" Comparator<A> fCmp = Comparator.<A, A>comparing(o -> o).thenComparingDouble(o -> Float.valueOf(0));",
" Comparator<A> dCmp = Comparator.<A, A>comparing(o -> o).thenComparingDouble(o -> Double.valueOf(0));",
" Comparator<A> fCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparingDouble(o -> Float.valueOf(0));",
" Comparator<A> dCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparingDouble(o -> Double.valueOf(0));",
"}")
.addOutputLines(
"out/A.java",
"import java.util.Comparator;",
"",
"interface A extends Comparable<A> {",
" Comparator<A> bCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Short.valueOf((short) 0));",
" Comparator<A> bCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparing(o -> Byte.valueOf((byte) 0));",
" Comparator<A> cCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparing(o -> Character.valueOf((char) 0));",
" Comparator<A> sCmp =",
" Comparator.<A, A>comparing(o -> o).thenComparing(o -> Short.valueOf((short) 0));",
" Comparator<A> iCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Integer.valueOf(0));",
" Comparator<A> lCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Long.valueOf(0));",
" Comparator<A> fCmp = Comparator.<A, A>comparing(o -> o).thenComparing(o -> Float.valueOf(0));",

View File

@@ -9,16 +9,16 @@ import org.junit.jupiter.api.Test;
// XXX: The tests below show that `String.valueOf((String) null)` may be simplified, but
// `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 RedundantStringConversionCheckTest {
final class RedundantStringConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversionCheck.class, getClass());
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass());
private final CompilationTestHelper customizedCompilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversionCheck.class, getClass())
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(RedundantStringConversionCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(RedundantStringConversion.class, getClass());
@Test
void identificationOfIdentityTransformation() {
@@ -124,7 +124,7 @@ final class RedundantStringConversionCheckTest {
" s + String.valueOf((String) null),",
" s + String.valueOf(null),",
" s + String.valueOf(new char[0]),",
"",
" //",
" 42 + this.toString(),",
" 42 + super.toString(),",
" 42 + i.toString(),",
@@ -147,7 +147,7 @@ final class RedundantStringConversionCheckTest {
" String.valueOf((String) null) + s,",
" String.valueOf(null) + s,",
" String.valueOf(new char[0]) + s,",
"",
" //",
" this.toString() + 42,",
" super.toString() + 42,",
" i.toString() + 42,",
@@ -175,10 +175,7 @@ final class RedundantStringConversionCheckTest {
"",
" int[] m2() {",
" return new int[] {",
" 1 + 1,",
" 1 - 1,",
" 1 * 1,",
" 1 / 1,",
" 1 + 1, 1 - 1, 1 * 1, 1 / 1,",
" };",
" }",
"}")
@@ -284,9 +281,9 @@ final class RedundantStringConversionCheckTest {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Preconditions.checkState;",
"import static com.google.common.base.Preconditions.checkArgument;",
"import static com.google.common.base.Preconditions.checkNotNull;",
"import static com.google.common.base.Preconditions.checkState;",
"import static com.google.common.base.Verify.verify;",
"import static com.google.common.base.Verify.verifyNotNull;",
"",

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RefasterAnyOfUsageCheckTest {
final class RefasterAnyOfUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RefasterAnyOfUsageCheck.class, getClass());
CompilationTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsageCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
@Test
void identification() {

View File

@@ -1,191 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static java.util.function.Function.identity;
import static java.util.function.Predicate.not;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.junit.ComparisonFailure;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
final class RefasterCheckTest {
/** The names of all Refaster template groups defined in this module. */
private static final ImmutableSet<String> TEMPLATE_GROUPS =
ImmutableSet.of(
"AssertJ",
"AssertJBigDecimal",
"AssertJBigInteger",
"AssertJBoolean",
"AssertJByte",
"AssertJCharSequence",
"AssertJDouble",
"AssertJEnumerable",
"AssertJFloat",
"AssertJInteger",
"AssertJLong",
"AssertJNumber",
"AssertJMap",
"AssertJObject",
"AssertJOptional",
"AssertJShort",
"AssertJString",
"AssertJThrowingCallable",
"Assorted",
"BigDecimal",
"Collection",
"Comparator",
"DoubleStream",
"Equality",
"ImmutableList",
"ImmutableListMultimap",
"ImmutableMap",
"ImmutableMultiset",
"ImmutableSet",
"ImmutableSetMultimap",
"ImmutableSortedMap",
"ImmutableSortedMultiset",
"ImmutableSortedSet",
"IntStream",
"JUnit",
"LongStream",
"MapEntry",
"Mockito",
"Multimap",
"Null",
"Optional",
"Primitive",
"Reactor",
"RxJava2Adapter",
"Stream",
"String",
"TestNGToAssertJ",
"Time",
"WebClient");
/**
* Matches the parts of the fully-qualified name of a template class that should be removed in
* order to produce the associated {@link #TEMPLATE_GROUPS template group name}.
*/
private static final Pattern TEMPLATE_FQCN_TRIM_FOR_GROUP_NAME =
Pattern.compile(".*\\.|Templates\\$.*");
/**
* A mapping from template group names to associated template names.
*
* <p>In effect, the values correspond to nested classes that represent individual Refaster
* templates, while the keys correspond to the associated top-level "aggregator" classes.
*/
private static final ImmutableSetMultimap<String, String> TEMPLATES_BY_GROUP =
indexTemplateNamesByGroup(RefasterCheck.ALL_CODE_TRANSFORMERS.get().keySet());
/** Returns every known template group name as a parameterized test argument. */
@SuppressWarnings("UnusedMethod" /* Used as a `@MethodSource`. */)
private static Stream<Arguments> templateGroupsUnderTest() {
// XXX: Drop the filter once we have added tests for AssertJ!
return TEMPLATES_BY_GROUP.keySet().stream().filter(not("AssertJ"::equals)).map(Arguments::of);
}
/**
* Returns every known (template group name, template name) pair as a parameterized test argument.
*/
@SuppressWarnings("UnusedMethod" /* Used as a `@MethodSource`. */)
private static Stream<Arguments> templatesUnderTest() {
// XXX: Drop the filter once we have added tests for AssertJ!
return TEMPLATES_BY_GROUP.entries().stream()
.filter(e -> !"AssertJ".equals(e.getKey()))
.map(e -> arguments(e.getKey(), e.getValue()));
}
/**
* Verifies that {@link RefasterCheck#loadAllCodeTransformers} finds at least one code transformer
* for all of the {@link #TEMPLATE_GROUPS}.
*
* <p>This test is just as much about ensuring that {@link #TEMPLATE_GROUPS} is exhaustive, so
* that in turn {@link #replacement}'s coverage is exhaustive.
*/
@Test
void loadAllCodeTransformers() {
assertThat(TEMPLATES_BY_GROUP.keySet()).hasSameElementsAs(TEMPLATE_GROUPS);
}
/**
* Verifies for each of the {@link #TEMPLATE_GROUPS} that the associated code transformers have
* the desired effect.
*/
@MethodSource("templateGroupsUnderTest")
@ParameterizedTest
void replacement(String group) {
verifyRefactoring(group, namePattern(group));
}
/**
* Verifies that all loaded Refaster templates are covered by at least one test.
*
* <p>Note that this doesn't guarantee full coverage: this test cannot ascertain that all {@link
* com.google.errorprone.refaster.Refaster#anyOf} branches are tested. Idem for {@link
* com.google.errorprone.refaster.annotation.BeforeTemplate} methods in case there are multiple .
*/
@MethodSource("templatesUnderTest")
@ParameterizedTest
void coverage(String group, String template) {
assertThatCode(() -> verifyRefactoring(group, namePattern(group, template)))
.withFailMessage(
"Template %s does not affect the tests for group %s; is it tested?", template, group)
.isInstanceOf(ComparisonFailure.class);
}
private static ImmutableSetMultimap<String, String> indexTemplateNamesByGroup(
ImmutableSet<String> templateNames) {
return templateNames.stream()
.collect(
toImmutableSetMultimap(
n -> TEMPLATE_FQCN_TRIM_FOR_GROUP_NAME.matcher(n).replaceAll(""), identity()));
}
private static String namePattern(String groupName, String excludedTemplate) {
return "(?!" + Pattern.quote(excludedTemplate) + ')' + namePattern(groupName);
}
private static String namePattern(String groupName) {
return Pattern.compile(Pattern.quote(groupName)) + "Templates.*";
}
private void verifyRefactoring(String groupName, String templateNamePattern) {
createRestrictedRefactoringTestHelper(templateNamePattern)
.addInput(groupName + "TemplatesTestInput.java")
.addOutput(groupName + "TemplatesTestOutput.java")
.doTest(TestMode.TEXT_MATCH);
}
private BugCheckerRefactoringTestHelper createRestrictedRefactoringTestHelper(
String namePattern) {
return BugCheckerRefactoringTestHelper.newInstance(
RefasterCheck.class, getRefasterTemplateCollectionClass())
.setArgs("-XepOpt:Refaster:NamePattern=" + namePattern);
}
/**
* Returns an arbitrary Refaster template collection class inside the {@code
* tech.picnic.errorprone.refastertemplates} package, enabling {@link
* BugCheckerRefactoringTestHelper} to load test resources from said package.
*/
private Class<?> getRefasterTemplateCollectionClass() {
try {
return Class.forName(
"tech.picnic.errorprone.refastertemplates.AssortedTemplates",
/* initialize= */ false,
getClass().getClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Failed to load Refaster template collection class", e);
}
}
}

View File

@@ -1,86 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestMappingAnnotationCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestMappingAnnotationCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.io.InputStream;",
"import java.time.ZoneId;",
"import java.util.Locale;",
"import java.util.TimeZone;",
"import javax.servlet.http.HttpServletRequest;",
"import javax.servlet.http.HttpServletResponse;",
"import org.springframework.http.HttpMethod;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PatchMapping;",
"import org.springframework.web.bind.annotation.PathVariable;",
"import org.springframework.web.bind.annotation.PostMapping;",
"import org.springframework.web.bind.annotation.PutMapping;",
"import org.springframework.web.bind.annotation.RequestBody;",
"import org.springframework.web.bind.annotation.RequestHeader;",
"import org.springframework.web.bind.annotation.RequestMapping;",
"import org.springframework.web.bind.annotation.RequestMethod;",
"import org.springframework.web.bind.annotation.RequestParam;",
"import org.springframework.web.context.request.NativeWebRequest;",
"import org.springframework.web.context.request.WebRequest;",
"import org.springframework.web.server.ServerWebExchange;",
"import org.springframework.web.util.UriBuilder;",
"import org.springframework.web.util.UriComponentsBuilder;",
"",
"interface A {",
" A noMapping();",
" A noMapping(String param);",
" @DeleteMapping A properNoParameters();",
" @GetMapping A properPathVariable(@PathVariable String param);",
" @PatchMapping A properRequestBody(@RequestBody String body);",
" @PostMapping A properRequestHeader(@RequestHeader String header);",
" @PutMapping A properRequestParam(@RequestParam String param);",
" @RequestMapping A properInputStream(InputStream input);",
" @RequestMapping A properZoneId(ZoneId zoneId);",
" @RequestMapping A properLocale(Locale locale);",
" @RequestMapping A properTimeZone(TimeZone timeZone);",
" @RequestMapping A properHttpServletRequest(HttpServletRequest request);",
" @RequestMapping A properHttpServletResponse(HttpServletResponse response);",
" @RequestMapping A properHttpMethod(HttpMethod method);",
" @RequestMapping A properNativeWebRequest(NativeWebRequest request);",
" @RequestMapping A properWebRequest(WebRequest request);",
" @RequestMapping A properServerWebExchange(ServerWebExchange exchange);",
" @RequestMapping A properServerUriBuilder(UriBuilder builder);",
" @RequestMapping A properServerUriComponentsBuilder(UriComponentsBuilder builder);",
"",
" // BUG: Diagnostic contains:",
" @DeleteMapping A delete(String param);",
"",
" // BUG: Diagnostic contains:",
" @GetMapping A get(String param);",
"",
" // BUG: Diagnostic contains:",
" @PatchMapping A patch(String param);",
"",
" // BUG: Diagnostic contains:",
" @PostMapping A post(String param);",
"",
" // BUG: Diagnostic contains:",
" @PutMapping A put(String param);",
"",
" // BUG: Diagnostic contains:",
" @RequestMapping A requestMultiple(String param, String param2);",
"",
" // BUG: Diagnostic contains:",
" @RequestMapping A requestFirstParamViolation(String param, @PathVariable String param2);",
"",
" // BUG: Diagnostic contains:",
" @RequestMapping A requestSecondParamViolation(@RequestBody String param, String param2);",
"}")
.doTest();
}
}

View File

@@ -0,0 +1,132 @@
package tech.picnic.errorprone.bugpatterns;
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
.addSourceLines(
"A.java",
"import java.io.InputStream;",
"import java.time.ZoneId;",
"import java.util.Locale;",
"import java.util.TimeZone;",
"import javax.servlet.http.HttpServletRequest;",
"import javax.servlet.http.HttpServletResponse;",
"import org.springframework.http.HttpMethod;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PatchMapping;",
"import org.springframework.web.bind.annotation.PathVariable;",
"import org.springframework.web.bind.annotation.PostMapping;",
"import org.springframework.web.bind.annotation.PutMapping;",
"import org.springframework.web.bind.annotation.RequestAttribute;",
"import org.springframework.web.bind.annotation.RequestBody;",
"import org.springframework.web.bind.annotation.RequestHeader;",
"import org.springframework.web.bind.annotation.RequestMapping;",
"import org.springframework.web.bind.annotation.RequestParam;",
"import org.springframework.web.context.request.NativeWebRequest;",
"import org.springframework.web.context.request.WebRequest;",
"import org.springframework.web.server.ServerWebExchange;",
"import org.springframework.web.util.UriBuilder;",
"import org.springframework.web.util.UriComponentsBuilder;",
"",
"interface A {",
" A noMapping();",
"",
" A noMapping(String param);",
"",
" @DeleteMapping",
" A properNoParameters();",
"",
" @GetMapping",
" A properPathVariable(@PathVariable String param);",
"",
" @PatchMapping",
" A properRequestAttribute(@RequestAttribute String attribute);",
"",
" @PostMapping",
" A properRequestBody(@RequestBody String body);",
"",
" @PutMapping",
" A properRequestHeader(@RequestHeader String header);",
"",
" @RequestMapping",
" A properRequestParam(@RequestParam String param);",
"",
" @RequestMapping",
" A properInputStream(InputStream input);",
"",
" @RequestMapping",
" A properZoneId(ZoneId zoneId);",
"",
" @RequestMapping",
" A properLocale(Locale locale);",
"",
" @RequestMapping",
" A properTimeZone(TimeZone timeZone);",
"",
" @RequestMapping",
" A properHttpServletRequest(HttpServletRequest request);",
"",
" @RequestMapping",
" A properHttpServletResponse(HttpServletResponse response);",
"",
" @RequestMapping",
" A properHttpMethod(HttpMethod method);",
"",
" @RequestMapping",
" A properNativeWebRequest(NativeWebRequest request);",
"",
" @RequestMapping",
" A properWebRequest(WebRequest request);",
"",
" @RequestMapping",
" A properServerWebExchange(ServerWebExchange exchange);",
"",
" @RequestMapping",
" A properServerUriBuilder(UriBuilder builder);",
"",
" @RequestMapping",
" A properServerUriComponentsBuilder(UriComponentsBuilder builder);",
"",
" @DeleteMapping",
" // BUG: Diagnostic contains:",
" A delete(String param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A get(String param);",
"",
" @PatchMapping",
" // BUG: Diagnostic contains:",
" A patch(String param);",
"",
" @PostMapping",
" // BUG: Diagnostic contains:",
" A post(String param);",
"",
" @PutMapping",
" // BUG: Diagnostic contains:",
" A put(String param);",
"",
" @RequestMapping",
" // BUG: Diagnostic contains:",
" A requestMultiple(String param, String param2);",
"",
" @RequestMapping",
" // BUG: Diagnostic contains:",
" A requestFirstParamViolation(String param, @PathVariable String param2);",
"",
" @RequestMapping",
" // BUG: Diagnostic contains:",
" A requestSecondParamViolation(@RequestBody String param, String param2);",
"}")
.doTest();
}
}

View File

@@ -3,9 +3,9 @@ package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestParamTypeCheckTest {
final class RequestParamTypeTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestParamTypeCheck.class, getClass());
CompilationTestHelper.newInstance(RequestParamType.class, getClass());
@Test
void identification() {
@@ -28,23 +28,36 @@ final class RequestParamTypeCheckTest {
"import org.springframework.web.bind.annotation.RequestParam;",
"",
"interface A {",
" @PostMapping A properRequestParam(@RequestBody String body);",
" @GetMapping A properRequestParam(@RequestParam int param);",
" @GetMapping A properRequestParam(@RequestParam List<String> param);",
" @PostMapping A properRequestParam(@RequestBody String body, @RequestParam Set<String> param);",
" @PutMapping A properRequestParam(@RequestBody String body, @RequestParam Map<String, String> param);",
" @PostMapping",
" A properRequestParam(@RequestBody String body);",
"",
" // BUG: Diagnostic contains:",
" @GetMapping A get(@RequestParam ImmutableBiMap<String, String> param);",
" @GetMapping",
" A properRequestParam(@RequestParam int param);",
"",
" // BUG: Diagnostic contains:",
" @PostMapping A post(@Nullable @RequestParam ImmutableList<String> param);",
" @GetMapping",
" A properRequestParam(@RequestParam List<String> param);",
"",
" // BUG: Diagnostic contains:",
" @PutMapping A put(@RequestBody String body, @RequestParam ImmutableSet<String> param);",
" @PostMapping",
" A properRequestParam(@RequestBody String body, @RequestParam Set<String> param);",
"",
" @PutMapping",
" A properRequestParam(@RequestBody String body, @RequestParam Map<String, String> param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" @DeleteMapping A delete(@RequestBody String body, @RequestParam ImmutableMap<String, String> param);",
" A get(@RequestParam ImmutableBiMap<String, String> param);",
"",
" @PostMapping",
" // BUG: Diagnostic contains:",
" A post(@Nullable @RequestParam ImmutableList<String> param);",
"",
" @PutMapping",
" // BUG: Diagnostic contains:",
" A put(@RequestBody String body, @RequestParam ImmutableSet<String> param);",
"",
" @DeleteMapping",
" // BUG: Diagnostic contains:",
" A delete(@RequestBody String body, @RequestParam ImmutableMap<String, String> param);",
"",
" void negative(ImmutableSet<Integer> set, ImmutableMap<String, String> map);",
"}")

View File

@@ -4,14 +4,12 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
final class ScheduledTransactionTraceCheckTest {
final class ScheduledTransactionTraceTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ScheduledTransactionTraceCheck.class, getClass());
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(ScheduledTransactionTraceCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(ScheduledTransactionTrace.class, getClass());
@Test
void identification() {
@@ -45,10 +43,7 @@ final class ScheduledTransactionTraceCheckTest {
.doTest();
}
// XXX: Enable this test for all JREs once https://github.com/google/error-prone/pull/2820 is
// merged and released.
@Test
@DisabledForJreRange(min = JRE.JAVA_12)
void replacement() {
refactoringTestHelper
.addInputLines(

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class Slf4jLogStatementCheckTest {
final class Slf4JLogStatementTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(Slf4jLogStatementCheck.class, getClass());
CompilationTestHelper.newInstance(Slf4jLogStatement.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(Slf4jLogStatementCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(Slf4jLogStatement.class, getClass());
@Test
void identification() {
@@ -44,7 +44,8 @@ final class Slf4jLogStatementCheckTest {
" LOG.info(marker, s, o, t);",
"",
" LOG.warn(FMT0);",
" // BUG: Diagnostic contains: Log statement contains 0 placeholders, but specifies 1 matching argument(s)",
" // BUG: Diagnostic contains: Log statement contains 0 placeholders, but specifies 1 matching",
" // argument(s)",
" LOG.error(FMT0, o);",
" LOG.trace(FMT0, t);",
" // BUG: Diagnostic contains:",
@@ -56,13 +57,15 @@ final class Slf4jLogStatementCheckTest {
" // BUG: Diagnostic contains:",
" LOG.trace(marker, FMT0, o, t);",
"",
" // BUG: Diagnostic contains: Log statement contains 1 placeholders, but specifies 0 matching argument(s)",
" // BUG: Diagnostic contains: Log statement contains 1 placeholders, but specifies 0 matching",
" // argument(s)",
" LOG.debug(FMT1);",
" LOG.info(FMT1, o);",
" // BUG: Diagnostic contains:",
" LOG.warn(FMT1, t);",
" LOG.error(FMT1, o, t);",
" // BUG: Diagnostic contains: Log statement contains 1 placeholders, but specifies 2 matching argument(s)",
" // BUG: Diagnostic contains: Log statement contains 1 placeholders, but specifies 2 matching",
" // argument(s)",
" LOG.trace(FMT1, o, o);",
" // BUG: Diagnostic contains:",
" LOG.debug(FMT1, o, o, t);",

View File

@@ -5,11 +5,11 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class SpringMvcAnnotationCheckTest {
final class SpringMvcAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(SpringMvcAnnotationCheck.class, getClass());
CompilationTestHelper.newInstance(SpringMvcAnnotation.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(SpringMvcAnnotationCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(SpringMvcAnnotation.class, getClass());
@Test
void identification() {
@@ -19,9 +19,9 @@ final class SpringMvcAnnotationCheckTest {
"import static org.springframework.web.bind.annotation.RequestMethod.DELETE;",
"import static org.springframework.web.bind.annotation.RequestMethod.GET;",
"import static org.springframework.web.bind.annotation.RequestMethod.HEAD;",
"import static org.springframework.web.bind.annotation.RequestMethod.PATCH;",
"import static org.springframework.web.bind.annotation.RequestMethod.POST;",
"import static org.springframework.web.bind.annotation.RequestMethod.PUT;",
"import static org.springframework.web.bind.annotation.RequestMethod.PATCH;",
"",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
@@ -32,28 +32,53 @@ final class SpringMvcAnnotationCheckTest {
"import org.springframework.web.bind.annotation.RequestMethod;",
"",
"interface A {",
" @RequestMapping A simple();",
" @RequestMapping(method = {}) A explicitDefault();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = RequestMethod.GET) A get();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {RequestMethod.POST}) A post();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PUT}) A put();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {DELETE}) A delete();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PATCH}) A patch();",
" @RequestMapping(method = HEAD) A head();",
" @RequestMapping(method = RequestMethod.OPTIONS) A options();",
" @RequestMapping(method = {GET, POST}) A simpleMix();",
" @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}) A verboseMix();",
" @RequestMapping",
" A simple();",
"",
" @DeleteMapping A properDelete();",
" @GetMapping A properGet();",
" @PatchMapping A properPatch();",
" @PostMapping A properPost();",
" @PutMapping A properPut();",
" @RequestMapping(method = {})",
" A explicitDefault();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = RequestMethod.GET)",
" A get();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {RequestMethod.POST})",
" A post();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PUT})",
" A put();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {DELETE})",
" A delete();",
" // BUG: Diagnostic contains:",
" @RequestMapping(method = {PATCH})",
" A patch();",
"",
" @RequestMapping(method = HEAD)",
" A head();",
"",
" @RequestMapping(method = RequestMethod.OPTIONS)",
" A options();",
"",
" @RequestMapping(method = {GET, POST})",
" A simpleMix();",
"",
" @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})",
" A verboseMix();",
"",
" @DeleteMapping",
" A properDelete();",
"",
" @GetMapping",
" A properGet();",
"",
" @PatchMapping",
" A properPatch();",
"",
" @PostMapping",
" A properPost();",
"",
" @PutMapping",
" A properPut();",
"}")
.doTest();
}
@@ -71,11 +96,25 @@ final class SpringMvcAnnotationCheckTest {
"import org.springframework.web.bind.annotation.RequestMethod;",
"",
"interface A {",
" @RequestMapping(method = RequestMethod.GET) A simple();",
" @RequestMapping(path = \"/foo/bar\", method = POST) A prefixed();",
" @RequestMapping(method = {RequestMethod.DELETE}, path = \"/foo/bar\") A suffixed();",
" @RequestMapping(path = \"/foo/bar\", method = {PUT}, consumes = {\"a\", \"b\"}) A surrounded();",
" @RequestMapping(method = {PATCH}) A curly();",
" @RequestMapping(method = RequestMethod.GET)",
" A simple();",
"",
" @RequestMapping(path = \"/foo/bar\", method = POST)",
" A prefixed();",
"",
" @RequestMapping(",
" method = {RequestMethod.DELETE},",
" path = \"/foo/bar\")",
" A suffixed();",
"",
" @RequestMapping(",
" path = \"/foo/bar\",",
" method = {PUT},",
" consumes = {\"a\", \"b\"})",
" A surrounded();",
"",
" @RequestMapping(method = {PATCH})",
" A curly();",
"}")
.addOutputLines(
"out/A.java",
@@ -92,11 +131,22 @@ final class SpringMvcAnnotationCheckTest {
"import org.springframework.web.bind.annotation.RequestMethod;",
"",
"interface A {",
" @GetMapping() A simple();",
" @PostMapping(path = \"/foo/bar\") A prefixed();",
" @DeleteMapping(path = \"/foo/bar\") A suffixed();",
" @PutMapping(path = \"/foo/bar\", consumes = {\"a\", \"b\"}) A surrounded();",
" @PatchMapping() A curly();",
" @GetMapping()",
" A simple();",
"",
" @PostMapping(path = \"/foo/bar\")",
" A prefixed();",
"",
" @DeleteMapping(path = \"/foo/bar\")",
" A suffixed();",
"",
" @PutMapping(",
" path = \"/foo/bar\",",
" consumes = {\"a\", \"b\"})",
" A surrounded();",
"",
" @PatchMapping()",
" A curly();",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -7,28 +7,28 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StaticImportCheckTest {
final class StaticImportTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StaticImportCheck.class, getClass());
CompilationTestHelper.newInstance(StaticImport.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(StaticImportCheck.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(StaticImport.class, getClass());
@Test
void candidateMethodsAreNotRedundant() {
assertThat(StaticImportCheck.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet())
.doesNotContainAnyElementsOf(StaticImportCheck.STATIC_IMPORT_CANDIDATE_TYPES);
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet())
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES);
}
@Test
void exemptedMembersAreNotVacuous() {
assertThat(StaticImportCheck.STATIC_IMPORT_EXEMPTED_MEMBERS.keySet())
.isSubsetOf(StaticImportCheck.STATIC_IMPORT_CANDIDATE_TYPES);
assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.keySet())
.isSubsetOf(StaticImport.STATIC_IMPORT_CANDIDATE_TYPES);
}
@Test
void exemptedMembersAreNotRedundant() {
assertThat(StaticImportCheck.STATIC_IMPORT_EXEMPTED_MEMBERS.values())
.doesNotContainAnyElementsOf(StaticImportCheck.STATIC_IMPORT_EXEMPTED_IDENTIFIERS);
assertThat(StaticImport.STATIC_IMPORT_EXEMPTED_MEMBERS.values())
.doesNotContainAnyElementsOf(StaticImport.STATIC_IMPORT_EXEMPTED_IDENTIFIERS);
}
@Test
@@ -51,9 +51,8 @@ final class StaticImportCheckTest {
"import java.nio.charset.StandardCharsets;",
"import java.time.ZoneOffset;",
"import java.util.Optional;",
"import java.util.function.Predicate;",
"import java.util.UUID;",
"import org.springframework.boot.test.context.SpringBootTest;",
"import java.util.function.Predicate;",
"import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;",
"import org.springframework.http.MediaType;",
"",
@@ -135,10 +134,10 @@ final class StaticImportCheckTest {
"import java.util.Objects;",
"import java.util.regex.Pattern;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.springframework.format.annotation.DateTimeFormat;",
"import org.springframework.format.annotation.DateTimeFormat.ISO;",
"import org.springframework.boot.test.context.SpringBootTest;",
"import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;",
"import org.springframework.format.annotation.DateTimeFormat;",
"import org.springframework.format.annotation.DateTimeFormat.ISO;",
"import org.springframework.http.MediaType;",
"",
"class A {",
@@ -181,15 +180,14 @@ final class StaticImportCheckTest {
" @DateTimeFormat(iso = ISO.TIME) String time) {}",
"",
" @BugPattern(",
" name = \"TestBugPattern\",",
" summary = \"\",",
" linkType = BugPattern.LinkType.NONE,",
" severity = SeverityLevel.SUGGESTION,",
" tags = BugPattern.StandardTags.SIMPLIFICATION)",
" summary = \"\",",
" linkType = BugPattern.LinkType.NONE,",
" severity = SeverityLevel.SUGGESTION,",
" tags = BugPattern.StandardTags.SIMPLIFICATION)",
" static final class TestBugPattern {}",
"",
" @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)",
" final class Test {}",
" @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)",
" final class Test {}",
"}")
.addOutputLines(
"out/A.java",
@@ -250,10 +248,7 @@ final class StaticImportCheckTest {
" Object o = UTF_8;",
"",
" ImmutableSet.of(",
" MediaType.ALL,",
" APPLICATION_XHTML_XML,",
" TEXT_HTML,",
" MediaType.valueOf(\"image/webp\"));",
" MediaType.ALL, APPLICATION_XHTML_XML, TEXT_HTML, MediaType.valueOf(\"image/webp\"));",
"",
" Pattern.compile(\"\", CASE_INSENSITIVE);",
" }",
@@ -268,16 +263,11 @@ final class StaticImportCheckTest {
" @DateTimeFormat(iso = DATE_TIME) String dateTime,",
" @DateTimeFormat(iso = TIME) String time) {}",
"",
" @BugPattern(",
" name = \"TestBugPattern\",",
" summary = \"\",",
" linkType = NONE,",
" severity = SUGGESTION,",
" tags = SIMPLIFICATION)",
" @BugPattern(summary = \"\", linkType = NONE, severity = SUGGESTION, tags = SIMPLIFICATION)",
" static final class TestBugPattern {}",
"",
" @SpringBootTest(webEnvironment = RANDOM_PORT)",
" final class Test {}",
" @SpringBootTest(webEnvironment = RANDOM_PORT)",
" final class Test {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -5,9 +5,9 @@ import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class TimeZoneUsageCheckTest {
final class TimeZoneUsageTest {
private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(TimeZoneUsageCheck.class, getClass())
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass())
.expectErrorMessage(
"X",
containsPattern(

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns;
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -17,10 +17,7 @@ import org.junit.jupiter.api.Test;
final class MethodMatcherFactoryTest {
/** A {@link BugChecker} which flags method invocations matched by {@link #TEST_MATCHER}. */
@BugPattern(
name = "MatchedMethodsFlagger",
severity = SUGGESTION,
summary = "Flags methods matched by the test matcher.")
@BugPattern(severity = SUGGESTION, summary = "Flags methods matched by the test matcher.")
public static final class MatchedMethodsFlagger extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
@@ -68,13 +65,21 @@ final class MethodMatcherFactoryTest {
"",
"public class A {",
" public void m1() {}",
"",
" public void m1(String s) {}",
"",
" public void m1(int i, int j) {}",
"",
" public void m2() {}",
"",
" public void m2(String s) {}",
"",
" public void m2(int i, int j) {}",
"",
" public void m3() {}",
"",
" public void m3(String s) {}",
"",
" public void m3(int i, int j) {}",
"}")
.addSourceLines(
@@ -83,13 +88,21 @@ final class MethodMatcherFactoryTest {
"",
"public class B {",
" public void m1() {}",
"",
" public void m1(String s) {}",
"",
" public void m1(int i, int j) {}",
"",
" public void m2() {}",
"",
" public void m2(String s) {}",
"",
" public void m2(int i, int j) {}",
"",
" public void m3() {}",
"",
" public void m3(String s) {}",
"",
" public void m3(int i, int j) {}",
"}")
.addSourceLines(
@@ -98,13 +111,21 @@ final class MethodMatcherFactoryTest {
"",
"public class A {",
" public static void m1() {}",
"",
" public static void m1(String s) {}",
"",
" public static void m1(int i, int j) {}",
"",
" public static void m2() {}",
"",
" public static void m2(String s) {}",
"",
" public static void m2(int i, int j) {}",
"",
" public static void m3() {}",
"",
" public static void m3(String s) {}",
"",
" public static void m3(int i, int j) {}",
"}")
.addSourceLines(
@@ -113,13 +134,21 @@ final class MethodMatcherFactoryTest {
"",
"public class B {",
" public static void m1() {}",
"",
" public static void m1(String s) {}",
"",
" public static void m1(int i, int j) {}",
"",
" public static void m2() {}",
"",
" public static void m2(String s) {}",
"",
" public static void m2(int i, int j) {}",
"",
" public static void m3() {}",
"",
" public static void m3(String s) {}",
"",
" public static void m3(int i, int j) {}",
"}")
.addSourceLines(

View File

@@ -0,0 +1,81 @@
package tech.picnic.errorprone.refastertemplates;
import static java.util.function.Predicate.not;
import com.google.common.collect.ImmutableSet;
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 tech.picnic.errorprone.refaster.test.RefasterTemplateCollection;
final class RefasterTemplatesTest {
/** The names of all Refaster template groups defined in this module. */
private static final ImmutableSet<Class<?>> TEMPLATE_COLLECTIONS =
ImmutableSet.of(
AssertJTemplates.class,
AssertJBigDecimalTemplates.class,
AssertJBigIntegerTemplates.class,
AssertJBooleanTemplates.class,
AssertJByteTemplates.class,
AssertJCharSequenceTemplates.class,
AssertJDoubleTemplates.class,
AssertJEnumerableTemplates.class,
AssertJFloatTemplates.class,
AssertJIntegerTemplates.class,
AssertJLongTemplates.class,
AssertJNumberTemplates.class,
AssertJMapTemplates.class,
AssertJObjectTemplates.class,
AssertJOptionalTemplates.class,
AssertJShortTemplates.class,
AssertJStringTemplates.class,
AssertJThrowingCallableTemplates.class,
AssortedTemplates.class,
BigDecimalTemplates.class,
CollectionTemplates.class,
ComparatorTemplates.class,
DoubleStreamTemplates.class,
EqualityTemplates.class,
ImmutableListTemplates.class,
ImmutableListMultimapTemplates.class,
ImmutableMapTemplates.class,
ImmutableMultisetTemplates.class,
ImmutableSetTemplates.class,
ImmutableSetMultimapTemplates.class,
ImmutableSortedMapTemplates.class,
ImmutableSortedMultisetTemplates.class,
ImmutableSortedSetTemplates.class,
IntStreamTemplates.class,
JUnitTemplates.class,
LongStreamTemplates.class,
MapEntryTemplates.class,
MockitoTemplates.class,
MultimapTemplates.class,
NullTemplates.class,
OptionalTemplates.class,
PrimitiveTemplates.class,
ReactorTemplates.class,
RxJava2AdapterTemplates.class,
StreamTemplates.class,
StringTemplates.class,
TestNGToAssertJTemplates.class,
TimeTemplates.class,
WebClientTemplates.class);
// XXX: Create a JUnit extension to automatically discover the template collections in a given
// context to make sure the list is exhaustive.
private static Stream<Arguments> validateTemplateCollectionTestCases() {
// XXX: Drop the filter once we have added tests for AssertJ! We can then also replace this
// method with `@ValueSource(classes = {...})`.
return TEMPLATE_COLLECTIONS.stream()
.filter(not(AssertJTemplates.class::equals))
.map(Arguments::arguments);
}
@MethodSource("validateTemplateCollectionTestCases")
@ParameterizedTest
void validateTemplateCollection(Class<?> clazz) {
RefasterTemplateCollection.validate(clazz);
}
}

View File

@@ -7,6 +7,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractBigDecimalAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBigDecimalTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -7,6 +7,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractBigDecimalAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBigDecimalTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -7,6 +7,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import java.math.BigInteger;
import org.assertj.core.api.AbstractBigIntegerAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBigIntegerTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -7,6 +7,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import java.math.BigInteger;
import org.assertj.core.api.AbstractBigIntegerAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBigIntegerTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -4,14 +4,15 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractBooleanAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
AbstractBooleanAssert<?> testAbstractBooleanAssertIsEqualTo() {
return assertThat(true).isNotEqualTo(!false);
return assertThat(true).isNotEqualTo(!Boolean.FALSE);
}
AbstractBooleanAssert<?> testAbstractBooleanAssertIsNotEqualTo() {
return assertThat(true).isEqualTo(!false);
return assertThat(true).isEqualTo(!Boolean.FALSE);
}
ImmutableSet<AbstractBooleanAssert<?>> testAbstractBooleanAssertIsTrue() {
@@ -23,7 +24,7 @@ final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
}
AbstractBooleanAssert<?> testAssertThatBooleanIsTrue() {
return assertThat(!true).isFalse();
return assertThat(!Boolean.TRUE).isFalse();
}
ImmutableSet<AbstractBooleanAssert<?>> testAbstractBooleanAssertIsFalse() {
@@ -35,6 +36,6 @@ final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
}
AbstractBooleanAssert<?> testAssertThatBooleanIsFalse() {
return assertThat(!true).isTrue();
return assertThat(!Boolean.TRUE).isTrue();
}
}

View File

@@ -4,14 +4,15 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractBooleanAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
AbstractBooleanAssert<?> testAbstractBooleanAssertIsEqualTo() {
return assertThat(true).isEqualTo(false);
return assertThat(true).isEqualTo(Boolean.FALSE);
}
AbstractBooleanAssert<?> testAbstractBooleanAssertIsNotEqualTo() {
return assertThat(true).isNotEqualTo(false);
return assertThat(true).isNotEqualTo(Boolean.FALSE);
}
ImmutableSet<AbstractBooleanAssert<?>> testAbstractBooleanAssertIsTrue() {
@@ -23,7 +24,7 @@ final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
}
AbstractBooleanAssert<?> testAssertThatBooleanIsTrue() {
return assertThat(true).isTrue();
return assertThat(Boolean.TRUE).isTrue();
}
ImmutableSet<AbstractBooleanAssert<?>> testAbstractBooleanAssertIsFalse() {
@@ -35,6 +36,6 @@ final class AssertJBooleanTemplatesTest implements RefasterTemplateTestCase {
}
AbstractBooleanAssert<?> testAssertThatBooleanIsFalse() {
return assertThat(true).isFalse();
return assertThat(Boolean.TRUE).isFalse();
}
}

View File

@@ -6,6 +6,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractByteAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJByteTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -6,6 +6,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractByteAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJByteTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJCharSequenceTemplatesTest implements RefasterTemplateTestCase {
void testAssertThatCharSequenceIsEmpty() {

View File

@@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJCharSequenceTemplatesTest implements RefasterTemplateTestCase {
void testAssertThatCharSequenceIsEmpty() {

View File

@@ -6,6 +6,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractDoubleAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJDoubleTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -6,6 +6,7 @@ import static org.assertj.core.data.Percentage.withPercentage;
import com.google.common.collect.ImmutableSet;
import org.assertj.core.api.AbstractDoubleAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJDoubleTemplatesTest implements RefasterTemplateTestCase {
@Override

View File

@@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.assertj.core.api.EnumerableAssert;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
final class AssertJEnumableTemplatesTest implements RefasterTemplateTestCase {
@Override

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