diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c9d07f79..a3ecc330 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -42,9 +42,9 @@ Please replace this sentence with log output, if applicable. - Operating system (e.g. MacOS Monterey). -- Java version (i.e. `java --version`, e.g. `17.0.10`). -- Error Prone version (e.g. `2.25.0`). -- Error Prone Support version (e.g. `0.15.0`). +- Java version (i.e. `java --version`, e.g. `17.0.13`). +- Error Prone version (e.g. `2.35.1`). +- Error Prone Support version (e.g. `0.19.0`). ### Additional context diff --git a/.github/release.yml b/.github/release.yml index bbd41aea..febfe2b2 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -3,16 +3,16 @@ changelog: labels: - "ignore-changelog" categories: + - title: ":warning: Update considerations and deprecations" + labels: + - "breaking change" + - "deprecation" - title: ":rocket: New Error Prone checks and Refaster rules" labels: - "new feature" - title: ":sparkles: Improvements" labels: - "improvement" - - title: ":warning: Update considerations and deprecations" - labels: - - "breaking change" - - "deprecation" - title: ":bug: Bug fixes" labels: - "bug" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2a26423..74d4a4f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,30 +9,32 @@ jobs: build: strategy: matrix: - os: [ ubuntu-22.04 ] - jdk: [ 17.0.10, 21.0.2 ] + os: [ ubuntu-24.04 ] + jdk: [ 17.0.13, 21.0.5, 23.0.1 ] distribution: [ temurin ] experimental: [ false ] include: - os: macos-14 - jdk: 17.0.10 + jdk: 17.0.13 distribution: temurin experimental: false - os: windows-2022 - jdk: 17.0.10 + jdk: 17.0.13 distribution: temurin experimental: false runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.adoptium.net:443 github.com:443 jitpack.io:443 + objects.githubusercontent.com:443 repo.maven.apache.org:443 # We run the build twice for each supported JDK: once against the # original Error Prone release, using only Error Prone checks available @@ -40,11 +42,11 @@ jobs: # additionally enabling all checks defined in this project and any Error # Prone checks available only from other artifact repositories. - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: java-version: ${{ matrix.jdk }} java-distribution: ${{ matrix.distribution }} - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Display build environment details run: mvn --version - name: Build project against vanilla Error Prone, compile Javadoc @@ -52,6 +54,6 @@ jobs: - 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 + run: mvn dependency:purge-local-repository -DmanualInclude='${project.groupId}' -DresolutionFuzziness=groupId # XXX: Enable Codecov once we "go public". diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7882aee6..00461690 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,33 +19,34 @@ jobs: permissions: contents: read security-events: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.adoptium.net:443 api.github.com:443 github.com:443 objects.githubusercontent.com:443 repo.maven.apache.org:443 uploads.github.com:443 - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: - java-version: 17.0.10 + java-version: 17.0.13 java-distribution: temurin - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Initialize CodeQL - uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 with: languages: ${{ matrix.language }} - name: Perform minimal build if: matrix.language == 'java' run: mvn -T1C clean package -DskipTests -Dverification.skip - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/analyze@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 with: category: /language:${{ matrix.language }} diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index cb729d30..eb5084b0 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -9,10 +9,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block @@ -39,15 +39,15 @@ jobs: www.youtube.com:443 youtrack.jetbrains.com:443 - name: Check out code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: ruby/setup-ruby@d4526a55538b775af234ba4af27118ed6f8f6677 # v1.172.0 + - uses: ruby/setup-ruby@7d3497fd78c07c0d84ebafa58d8dac60cd1f0763 # v1.199.0 with: working-directory: ./website bundler-cache: true - name: Configure Github Pages - uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0 + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - name: Generate documentation run: ./generate-docs.sh - name: Build website with Jekyll @@ -68,13 +68,13 @@ jobs: permissions: id-token: write pages: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block diff --git a/.github/workflows/openssf-scorecard.yml b/.github/workflows/openssf-scorecard.yml index 0b757089..5d2ea817 100644 --- a/.github/workflows/openssf-scorecard.yml +++ b/.github/workflows/openssf-scorecard.yml @@ -18,34 +18,36 @@ jobs: contents: read security-events: write id-token: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.deps.dev:443 api.github.com:443 api.osv.dev:443 + api.scorecard.dev:443 api.securityscorecards.dev:443 - fulcio.sigstore.dev:443 github.com:443 + index.docker.io:443 oss-fuzz-build-logs.storage.googleapis.com:443 - rekor.sigstore.dev:443 - tuf-repo-cdn.sigstore.dev:443 + repo.maven.apache.org:443 + *.sigstore.dev:443 www.bestpractices.dev:443 - name: Check out code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Run OpenSSF Scorecard analysis - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif publish_results: ${{ github.ref == 'refs/heads/master' }} - name: Update GitHub's code scanning dashboard - uses: github/codeql-action/upload-sarif@47b3d888fe66b639e431abf22ebca059152f1eea # v3.24.5 + uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 with: sarif_file: results.sarif diff --git a/.github/workflows/pitest-analyze-pr.yml b/.github/workflows/pitest-analyze-pr.yml index d0763aeb..c003a4c0 100644 --- a/.github/workflows/pitest-analyze-pr.yml +++ b/.github/workflows/pitest-analyze-pr.yml @@ -9,23 +9,25 @@ permissions: contents: read jobs: analyze-pr: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.adoptium.net:443 github.com:443 + objects.githubusercontent.com:443 repo.maven.apache.org:443 - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: checkout-fetch-depth: 2 - java-version: 17.0.10 + java-version: 17.0.13 java-distribution: temurin - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Run Pitest # By running with features `+GIT(from[HEAD~1]), +gitci`, Pitest only # analyzes lines changed in the associated pull request, as GitHub @@ -36,7 +38,7 @@ jobs: - name: Aggregate Pitest reports run: mvn pitest-git:aggregate -DkilledEmoji=":tada:" -DmutantEmoji=":zombie:" -DtrailingText="Mutation testing report by [Pitest](https://pitest.org/). Review any surviving mutants by inspecting the line comments under [_Files changed_](${{ github.event.number }}/files)." - name: Upload Pitest reports as artifact - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: pitest-reports path: ./target/pit-reports-ci diff --git a/.github/workflows/pitest-update-pr.yml b/.github/workflows/pitest-update-pr.yml index 83937e9e..92c78957 100644 --- a/.github/workflows/pitest-update-pr.yml +++ b/.github/workflows/pitest-update-pr.yml @@ -17,25 +17,27 @@ jobs: checks: write contents: read pull-requests: write - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.adoptium.net:443 api.github.com:443 github.com:443 + objects.githubusercontent.com:443 repo.maven.apache.org:443 - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: - java-version: 17.0.10 + java-version: 17.0.13 java-distribution: temurin - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Download Pitest analysis artifact - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4 with: workflow: ${{ github.event.workflow_run.workflow_id }} name: pitest-reports diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml index d9f71aab..e5860315 100644 --- a/.github/workflows/run-integration-tests.yml +++ b/.github/workflows/run-integration-tests.yml @@ -2,8 +2,8 @@ # against the project, using the code found on the pull request branch. # XXX: Generalize this to a matrix build of multiple integration tests, # possibly using multiple JDK or OS versions. -# XXX: Investigate whether the comment can specify which integration tests run -# run. See this example of a dynamic build matrix: +# XXX: Investigate whether the comment can specify which integration tests run. +# See this example of a dynamic build matrix: # https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object name: "Integration tests" on: @@ -15,43 +15,45 @@ permissions: contents: read jobs: run-integration-tests: - runs-on: ubuntu-22.04 -# if: github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test') + runs-on: ubuntu-24.04 + # if: github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test') # XXX: Configure permissions. strategy: matrix: integration-test: [ "metrics", "checkstyle" ] name: On-demand integration test - steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + api.adoptium.net:443 checkstyle.org:443 github.com:443 + objects.githubusercontent.com:443 oss.sonatype.org:443 raw.githubusercontent.com:443 repo.maven.apache.org:443 repository.sonatype.org:443 - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: - checkout-ref: "refs/pull/894/head" - java-version: 17.0.10 + # checkout-ref: "refs/pull/894/head" + checkout-ref: "refs/pull/${{ github.event.issue.number }}/head" + java-version: 17.0.13 java-distribution: temurin - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Install project to local Maven repository run: mvn -T1C install -DskipTests -Dverification.skip - name: Run integration test run: xvfb-run "./integration-tests/${{ matrix.integration-test }}.sh" "${{ runner.temp }}/artifacts" - name: Upload artifacts on failure if: ${{ failure() }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: "integration-test-${{ matrix.integration-test }}" path: "${{ runner.temp }}/artifacts" - name: Remove installed project artifacts - run: mvn build-helper:remove-project-artifact + run: mvn dependency:purge-local-repository -DmanualInclude='${project.groupId}' -DresolutionFuzziness=groupId diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 1a7722b1..43aa6c4e 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -16,27 +16,31 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository permissions: contents: read - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Install Harden-Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: disable-sudo: true egress-policy: block allowed-endpoints: > + analysis-sensorcache-eu-central-1-prod.s3.amazonaws.com:443 + api.adoptium.net:443 + api.nuget.org:443 ea6ne4j2sb.execute-api.eu-central-1.amazonaws.com:443 github.com:443 + objects.githubusercontent.com:443 repo.maven.apache.org:443 sc-cleancode-sensorcache-eu-central-1-prod.s3.amazonaws.com:443 - scanner.sonarcloud.io:443 + *.sonarcloud.io:443 sonarcloud.io:443 - name: Check out code and set up JDK and Maven - uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0 + uses: s4u/setup-maven-action@382542f77617f34e56bf83868920a4d45b7451e7 # v1.16.0 with: checkout-fetch-depth: 0 - java-version: 17.0.10 + java-version: 17.0.13 java-distribution: temurin - maven-version: 3.9.6 + maven-version: 3.9.9 - name: Create missing `test` directory # XXX: Drop this step in favour of actually having a test. run: mkdir refaster-compiler/src/test diff --git a/.gitignore b/.gitignore index fbb0d366..8ed010f1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .DS_Store .factorypath .idea +!.idea/icon.svg .project .settings target diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 00000000..3579ea9f --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.renovaterc.json b/.renovaterc.json index 9f50ffa4..1a35a0fb 100644 --- a/.renovaterc.json +++ b/.renovaterc.json @@ -12,7 +12,7 @@ "separateMinorPatch": true }, { - "matchDepNames": [ + "matchPackageNames": [ "dawidd6/action-download-artifact", "github/codeql-action", "ruby/setup-ruby" diff --git a/README.md b/README.md index 6b08e469..838f2556 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,7 @@ channel; please see our [security policy][security] for details. [refaster]: https://errorprone.info/docs/refaster [refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java [refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ -[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96 +[reproducible-builds-badge]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jvm-repo-rebuild/reproducible-central/master/content/tech/picnic/error-prone-support/error-prone-support/badge.json [reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md [script-apply-error-prone-suggestions]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/apply-error-prone-suggestions.sh [script-run-branch-mutation-tests]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/run-branch-mutation-tests.sh diff --git a/documentation-support/pom.xml b/documentation-support/pom.xml index 373b2ce9..4b1aa06f 100644 --- a/documentation-support/pom.xml +++ b/documentation-support/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT documentation-support @@ -33,6 +33,15 @@ error_prone_test_helpers test + + ${project.groupId} + error-prone-utils + + + ${project.groupId} + refaster-test-support + test + com.fasterxml.jackson.core jackson-annotations diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java index 7ca51f68..467a664f 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java @@ -27,7 +27,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; -import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases; /** * An {@link Extractor} that describes how to extract data from classes that test a {@code @@ -40,7 +40,7 @@ import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases; @Immutable @AutoService(Extractor.class) @SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */) -public final class BugPatternTestExtractor implements Extractor { +public final class BugPatternTestExtractor implements Extractor { /** Instantiates a new {@link BugPatternTestExtractor} instance. */ public BugPatternTestExtractor() {} @@ -50,7 +50,7 @@ public final class BugPatternTestExtractor implements Extractor { } @Override - public Optional tryExtract(ClassTree tree, VisitorState state) { + public Optional tryExtract(ClassTree tree, VisitorState state) { BugPatternTestCollector collector = new BugPatternTestCollector(); collector.scan(tree, state); @@ -59,7 +59,7 @@ public final class BugPatternTestExtractor implements Extractor { .filter(not(ImmutableList::isEmpty)) .map( tests -> - new AutoValue_BugPatternTestExtractor_TestCases( + new AutoValue_BugPatternTestExtractor_BugPatternTestCases( state.getPath().getCompilationUnit().getSourceFile().toUri(), ASTHelpers.getSymbol(tree).className(), tests)); @@ -95,10 +95,10 @@ public final class BugPatternTestExtractor implements Extractor { .onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput") .namedAnyOf("addOutputLines", "expectUnchanged"); - private final List collectedTestCases = new ArrayList<>(); + private final List collectedBugPatternTestCases = new ArrayList<>(); - private ImmutableList getCollectedTests() { - return ImmutableList.copyOf(collectedTestCases); + private ImmutableList getCollectedTests() { + return ImmutableList.copyOf(collectedBugPatternTestCases); } @Override @@ -110,14 +110,14 @@ public final class BugPatternTestExtractor implements Extractor { classUnderTest -> { List entries = new ArrayList<>(); if (isReplacementTest) { - extractReplacementTestCases(node, entries, state); + extractReplacementBugPatternTestCases(node, entries, state); } else { - extractIdentificationTestCases(node, entries, state); + extractIdentificationBugPatternTestCases(node, entries, state); } if (!entries.isEmpty()) { - collectedTestCases.add( - new AutoValue_BugPatternTestExtractor_TestCase( + collectedBugPatternTestCases.add( + new AutoValue_BugPatternTestExtractor_BugPatternTestCase( classUnderTest, ImmutableList.copyOf(entries).reverse())); } }); @@ -140,7 +140,7 @@ public final class BugPatternTestExtractor implements Extractor { : Optional.empty(); } - private static void extractIdentificationTestCases( + private static void extractIdentificationBugPatternTestCases( MethodInvocationTree tree, List sink, VisitorState state) { if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) { String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class); @@ -155,11 +155,11 @@ public final class BugPatternTestExtractor implements Extractor { ExpressionTree receiver = ASTHelpers.getReceiver(tree); if (receiver instanceof MethodInvocationTree methodInvocation) { - extractIdentificationTestCases(methodInvocation, sink, state); + extractIdentificationBugPatternTestCases(methodInvocation, sink, state); } } - private static void extractReplacementTestCases( + private static void extractReplacementBugPatternTestCases( MethodInvocationTree tree, List sink, VisitorState state) { if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) { /* @@ -185,7 +185,7 @@ public final class BugPatternTestExtractor implements Extractor { ExpressionTree receiver = ASTHelpers.getReceiver(tree); if (receiver instanceof MethodInvocationTree methodInvocation) { - extractReplacementTestCases(methodInvocation, sink, state); + extractReplacementBugPatternTestCases(methodInvocation, sink, state); } } @@ -208,24 +208,26 @@ public final class BugPatternTestExtractor implements Extractor { } @AutoValue - @JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCases.class) - abstract static class TestCases { - static TestCases create(URI source, String testClass, ImmutableList testCases) { - return new AutoValue_BugPatternTestExtractor_TestCases(source, testClass, testCases); + @JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCases.class) + abstract static class BugPatternTestCases { + static BugPatternTestCases create( + URI source, String testClass, ImmutableList testCases) { + return new AutoValue_BugPatternTestExtractor_BugPatternTestCases( + source, testClass, testCases); } abstract URI source(); abstract String testClass(); - abstract ImmutableList testCases(); + abstract ImmutableList testCases(); } @AutoValue - @JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCase.class) - abstract static class TestCase { - static TestCase create(String classUnderTest, ImmutableList entries) { - return new AutoValue_BugPatternTestExtractor_TestCase(classUnderTest, entries); + @JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCase.class) + abstract static class BugPatternTestCase { + static BugPatternTestCase create(String classUnderTest, ImmutableList entries) { + return new AutoValue_BugPatternTestExtractor_BugPatternTestCase(classUnderTest, entries); } abstract String classUnderTest(); diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java index 2cad3ace..fd3f7c12 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java @@ -14,7 +14,6 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ServiceLoader; import javax.tools.JavaFileObject; @@ -87,6 +86,6 @@ final class DocumentationGeneratorTaskListener implements TaskListener { } private static String getSimpleClassName(URI path) { - return Paths.get(path).getFileName().toString().replace(".java", ""); + return Path.of(path).getFileName().toString().replace(".java", ""); } } diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java new file mode 100644 index 00000000..8094a354 --- /dev/null +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java @@ -0,0 +1,176 @@ +package tech.picnic.errorprone.documentation; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.errorprone.matchers.Matchers.isSubtypeOf; +import static java.util.stream.Collectors.joining; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.service.AutoService; +import com.google.auto.value.AutoValue; +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.VisitorState; +import com.google.errorprone.annotations.FormatMethod; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.matchers.Matcher; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import java.net.URI; +import java.util.Optional; +import java.util.regex.Pattern; +import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases; +import tech.picnic.errorprone.utils.SourceCode; + +/** + * An {@link Extractor} that describes how to extract data from Refaster rule input and output test + * classes. + */ +// XXX: Drop this extractor if/when the Refaster test framework is reimplemented such that tests can +// be located alongside rules, rather than in two additional resource files as currently required by +// `RefasterRuleCollection`. +@Immutable +@AutoService(Extractor.class) +@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */) +public final class RefasterRuleCollectionTestExtractor implements Extractor { + private static final Matcher IS_REFASTER_RULE_COLLECTION_TEST_CASE = + isSubtypeOf("tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase"); + private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test"); + private static final Pattern TEST_CLASS_FILE_NAME_PATTERN = + Pattern.compile(".*(Input|Output)\\.java"); + private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("test(.*)"); + private static final String LINE_SEPARATOR = "\n"; + private static final Splitter LINE_SPLITTER = Splitter.on(LINE_SEPARATOR); + + /** Instantiates a new {@link RefasterRuleCollectionTestExtractor} instance. */ + public RefasterRuleCollectionTestExtractor() {} + + @Override + public String identifier() { + return "refaster-rule-collection-test"; + } + + @Override + public Optional tryExtract(ClassTree tree, VisitorState state) { + if (!IS_REFASTER_RULE_COLLECTION_TEST_CASE.matches(tree, state)) { + return Optional.empty(); + } + + URI sourceFile = state.getPath().getCompilationUnit().getSourceFile().toUri(); + return Optional.of( + RefasterTestCases.create( + sourceFile, + getRuleCollectionName(tree), + isInputFile(sourceFile), + getRefasterTestCases(tree, state))); + } + + private static String getRuleCollectionName(ClassTree tree) { + String className = tree.getSimpleName().toString(); + + // XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key + // aspects of `RefasterRuleCollectionTestCase` subtypes. + return tryExtractPatternGroup(className, TEST_CLASS_NAME_PATTERN) + .orElseThrow( + violation( + "Refaster rule collection test class name '%s' does not match '%s'", + className, TEST_CLASS_NAME_PATTERN)); + } + + private static boolean isInputFile(URI sourceFile) { + String path = sourceFile.getPath(); + + // XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key + // aspects of `RefasterRuleCollectionTestCase` subtypes. + return "Input" + .equals( + tryExtractPatternGroup(path, TEST_CLASS_FILE_NAME_PATTERN) + .orElseThrow( + violation( + "Refaster rule collection test file name '%s' does not match '%s'", + path, TEST_CLASS_FILE_NAME_PATTERN))); + } + + private static ImmutableList getRefasterTestCases( + ClassTree tree, VisitorState state) { + return tree.getMembers().stream() + .filter(MethodTree.class::isInstance) + .map(MethodTree.class::cast) + .flatMap(m -> tryExtractRefasterTestCase(m, state).stream()) + .collect(toImmutableList()); + } + + private static Optional tryExtractRefasterTestCase( + MethodTree method, VisitorState state) { + return tryExtractPatternGroup(method.getName().toString(), TEST_METHOD_NAME_PATTERN) + .map(name -> RefasterTestCase.create(name, getFormattedSource(method, state))); + } + + /** + * Returns the source code for the specified method. + * + * @implNote This operation attempts to trim leading whitespace, such that the start and end of + * the method declaration are aligned. The implemented heuristic assumes that the code is + * formatted using Google Java Format. + */ + // XXX: Leading Javadoc and other comments are currently not extracted. Consider fixing this. + private static String getFormattedSource(MethodTree method, VisitorState state) { + String source = SourceCode.treeToString(method, state); + int finalNewline = source.lastIndexOf(LINE_SEPARATOR); + if (finalNewline < 0) { + return source; + } + + int indentation = Math.max(0, source.lastIndexOf(' ') - finalNewline); + String prefixToStrip = " ".repeat(indentation); + + return LINE_SPLITTER + .splitToStream(source) + .map(line -> line.startsWith(prefixToStrip) ? line.substring(indentation) : line) + .collect(joining(LINE_SEPARATOR)); + } + + private static Optional tryExtractPatternGroup(String input, Pattern pattern) { + java.util.regex.Matcher matcher = pattern.matcher(input); + return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty(); + } + + @FormatMethod + private static Supplier violation(String format, Object... args) { + return () -> new VerifyException(String.format(format, args)); + } + + @AutoValue + @JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases.class) + abstract static class RefasterTestCases { + static RefasterTestCases create( + URI source, + String ruleCollection, + boolean isInput, + ImmutableList testCases) { + return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases( + source, ruleCollection, isInput, testCases); + } + + abstract URI source(); + + abstract String ruleCollection(); + + abstract boolean isInput(); + + abstract ImmutableList testCases(); + } + + @AutoValue + @JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase.class) + abstract static class RefasterTestCase { + static RefasterTestCase create(String name, String content) { + return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase(name, content); + } + + abstract String name(); + + abstract String content(); + } +} diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java index 02d59b81..c8761281 100644 --- a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java @@ -7,10 +7,10 @@ import java.net.URI; import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCase; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases; import tech.picnic.errorprone.documentation.BugPatternTestExtractor.IdentificationTestEntry; import tech.picnic.errorprone.documentation.BugPatternTestExtractor.ReplacementTestEntry; -import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCase; -import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases; final class BugPatternTestExtractorTest { @Test @@ -269,11 +269,11 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "SingleFileCompilationTestHelperTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///SingleFileCompilationTestHelperTest.java"), "SingleFileCompilationTestHelperTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "SingleFileCompilationTestHelperTest.TestChecker", ImmutableList.of( IdentificationTestEntry.create( @@ -302,11 +302,11 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "SingleFileCompilationTestHelperWithSetArgsTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///SingleFileCompilationTestHelperWithSetArgsTest.java"), "SingleFileCompilationTestHelperWithSetArgsTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "SingleFileCompilationTestHelperWithSetArgsTest.TestChecker", ImmutableList.of( IdentificationTestEntry.create( @@ -335,11 +335,11 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "MultiFileCompilationTestHelperTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///MultiFileCompilationTestHelperTest.java"), "MultiFileCompilationTestHelperTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "MultiFileCompilationTestHelperTest.TestChecker", ImmutableList.of( IdentificationTestEntry.create( @@ -370,11 +370,11 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "SingleFileBugCheckerRefactoringTestHelperTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///SingleFileBugCheckerRefactoringTestHelperTest.java"), "SingleFileBugCheckerRefactoringTestHelperTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "SingleFileBugCheckerRefactoringTestHelperTest.TestChecker", ImmutableList.of( ReplacementTestEntry.create( @@ -408,12 +408,12 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest", - TestCases.create( + BugPatternTestCases.create( URI.create( "file:///SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.java"), "SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "SingleFileBugCheckerRefactoringTestHelperWithSetArgsFixChooserAndCustomTestModeTest.TestChecker", ImmutableList.of( ReplacementTestEntry.create( @@ -444,11 +444,11 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "MultiFileBugCheckerRefactoringTestHelperTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///MultiFileBugCheckerRefactoringTestHelperTest.java"), "MultiFileBugCheckerRefactoringTestHelperTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "MultiFileBugCheckerRefactoringTestHelperTest.TestChecker", ImmutableList.of( ReplacementTestEntry.create( @@ -484,16 +484,16 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "CompilationAndBugCheckerRefactoringTestHelpersTest", - TestCases.create( + BugPatternTestCases.create( URI.create("file:///CompilationAndBugCheckerRefactoringTestHelpersTest.java"), "CompilationAndBugCheckerRefactoringTestHelpersTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker", ImmutableList.of( IdentificationTestEntry.create( "A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))), - TestCase.create( + BugPatternTestCase.create( "CompilationAndBugCheckerRefactoringTestHelpersTest.TestChecker", ImmutableList.of( ReplacementTestEntry.create( @@ -532,17 +532,17 @@ final class BugPatternTestExtractorTest { verifyGeneratedFileContent( outputDirectory, "CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest", - TestCases.create( + BugPatternTestCases.create( URI.create( "file:///CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.java"), "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest", ImmutableList.of( - TestCase.create( + BugPatternTestCase.create( "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker", ImmutableList.of( IdentificationTestEntry.create( "A.java", "// BUG: Diagnostic contains:\nclass A {}\n"))), - TestCase.create( + BugPatternTestCase.create( "pkg.CompilationAndBugCheckerRefactoringTestHelpersWithCustomCheckerPackageAndNamesTest.CustomTestChecker2", ImmutableList.of( ReplacementTestEntry.create( @@ -550,9 +550,9 @@ final class BugPatternTestExtractorTest { } private static void verifyGeneratedFileContent( - Path outputDirectory, String testClass, TestCases expected) { + Path outputDirectory, String testClass, BugPatternTestCases expected) { assertThat(outputDirectory.resolve(String.format("bugpattern-test-%s.json", testClass))) .exists() - .returns(expected, path -> Json.read(path, TestCases.class)); + .returns(expected, path -> Json.read(path, BugPatternTestCases.class)); } } diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListenerTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListenerTest.java index 685c7cd6..4e9d175a 100644 --- a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListenerTest.java +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListenerTest.java @@ -10,7 +10,6 @@ import static org.junit.jupiter.api.condition.OS.WINDOWS; import com.google.auto.service.AutoService; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.common.collect.Streams; import com.google.errorprone.VisitorState; @@ -41,7 +40,8 @@ final class DocumentationGeneratorTaskListenerTest { entry -> AclEntry.newBuilder(entry) .setPermissions( - Sets.difference(entry.permissions(), ImmutableSet.of(ADD_SUBDIRECTORY))) + Sets.difference( + entry.permissions(), Sets.immutableEnumSet(ADD_SUBDIRECTORY))) .build()) .collect(toImmutableList())); diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractorTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractorTest.java new file mode 100644 index 00000000..020bf7d9 --- /dev/null +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractorTest.java @@ -0,0 +1,166 @@ +package tech.picnic.errorprone.documentation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import java.net.URI; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase; +import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases; + +final class RefasterRuleCollectionTestExtractorTest { + @Test + void noRefasterRuleTest(@TempDir Path outputDirectory) { + Compilation.compileWithDocumentationGenerator( + outputDirectory, "NoRefasterRuleTest.java", "public final class NoRefasterRuleTest {}"); + + assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory(); + } + + @Test + void invalidTestClassName(@TempDir Path outputDirectory) { + assertThatThrownBy( + () -> + Compilation.compileWithDocumentationGenerator( + outputDirectory, + "InvalidTestClassNameInput.java", + "import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;", + "", + "final class InvalidTestClassName implements RefasterRuleCollectionTestCase {}")) + .cause() + .isInstanceOf(VerifyException.class) + .hasMessage( + "Refaster rule collection test class name 'InvalidTestClassName' does not match '(.*)Test'"); + } + + @Test + void invalidFileName(@TempDir Path outputDirectory) { + assertThatThrownBy( + () -> + Compilation.compileWithDocumentationGenerator( + outputDirectory, + "InvalidFileNameTest.java", + "import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;", + "", + "final class InvalidFileNameTest implements RefasterRuleCollectionTestCase {}")) + .cause() + .isInstanceOf(VerifyException.class) + .hasMessage( + "Refaster rule collection test file name '/InvalidFileNameTest.java' does not match '.*(Input|Output)\\.java'"); + } + + @Test + void emptyRefasterRuleCollectionTestInput(@TempDir Path outputDirectory) { + Compilation.compileWithDocumentationGenerator( + outputDirectory, + "EmptyRefasterRuleCollectionTestInput.java", + "import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;", + "", + "final class EmptyRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {}"); + + verifyGeneratedFileContent( + outputDirectory, + "EmptyRefasterRuleCollectionTestInput", + RefasterTestCases.create( + URI.create("file:///EmptyRefasterRuleCollectionTestInput.java"), + "EmptyRefasterRuleCollection", + /* isInput= */ true, + ImmutableList.of())); + } + + @Test + void singletonRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) { + Compilation.compileWithDocumentationGenerator( + outputDirectory, + "SingletonRefasterRuleCollectionTestOutput.java", + "import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;", + "", + "final class SingletonRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {", + " int testMyRule() {", + " return 42;", + " }", + "}"); + + verifyGeneratedFileContent( + outputDirectory, + "SingletonRefasterRuleCollectionTestOutput", + RefasterTestCases.create( + URI.create("file:///SingletonRefasterRuleCollectionTestOutput.java"), + "SingletonRefasterRuleCollection", + /* isInput= */ false, + ImmutableList.of( + RefasterTestCase.create( + "MyRule", + """ + int testMyRule() { + return 42; + }""")))); + } + + @Test + void complexRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) { + Compilation.compileWithDocumentationGenerator( + outputDirectory, + "pkg/ComplexRefasterRuleCollectionTestInput.java", + "package pkg;", + "", + "import com.google.common.collect.ImmutableSet;", + "import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;", + "", + "final class ComplexRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {", + " private static final String IGNORED_CONSTANT = \"constant\";", + "", + " @Override", + " public ImmutableSet elidedTypesAndStaticImports() {", + " return ImmutableSet.of();", + " }", + "", + " /** Javadoc. */", + " String testFirstRule() {", + " return \"Don't panic\";", + " }", + "", + " // Comment.", + " String testSecondRule() {", + " return \"Carry a towel\";", + " }", + "", + " void testEmptyRule() {}", + "}"); + + verifyGeneratedFileContent( + outputDirectory, + "ComplexRefasterRuleCollectionTestInput", + RefasterTestCases.create( + URI.create("file:///pkg/ComplexRefasterRuleCollectionTestInput.java"), + "ComplexRefasterRuleCollection", + /* isInput= */ true, + ImmutableList.of( + RefasterTestCase.create( + "FirstRule", + """ + String testFirstRule() { + return "Don't panic"; + }"""), + RefasterTestCase.create( + "SecondRule", + """ + String testSecondRule() { + return "Carry a towel"; + }"""), + RefasterTestCase.create("EmptyRule", "void testEmptyRule() {}")))); + } + + private static void verifyGeneratedFileContent( + Path outputDirectory, String testIdentifier, RefasterTestCases expected) { + assertThat( + outputDirectory.resolve( + String.format("refaster-rule-collection-test-%s.json", testIdentifier))) + .exists() + .returns(expected, path -> Json.read(path, RefasterTestCases.class)); + } +} diff --git a/error-prone-contrib/pom.xml b/error-prone-contrib/pom.xml index 122e0c8a..5c9d5633 100644 --- a/error-prone-contrib/pom.xml +++ b/error-prone-contrib/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT error-prone-contrib @@ -55,7 +55,12 @@ ${project.groupId} refaster-support - provided + ${project.groupId} @@ -67,6 +72,11 @@ jackson-annotations provided + + com.github.ben-manes.caffeine + caffeine + provided + com.google.auto.service auto-service-annotations @@ -82,6 +92,11 @@ guava provided + + io.micrometer + micrometer-core + provided + io.projectreactor reactor-core @@ -122,6 +137,11 @@ jakarta.servlet-api test + + javax.annotation + javax.annotation-api + provided + javax.inject javax.inject @@ -191,7 +211,7 @@ org.openrewrite - rewrite-java-11 + rewrite-java-17 test @@ -282,6 +302,55 @@ -Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs + + + + + compile-refaster-test-input + + testCompile + + process-test-resources + + + ${project.basedir}/src/test/resources + + + **/*Input.java + + ${project.build.directory}/refaster-test-input + + + + compile-refaster-test-output + + testCompile + + process-test-resources + + + ${project.basedir}/src/test/resources + + + **/*Output.java + + ${project.build.directory}/refaster-test-output + + + diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsage.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsage.java new file mode 100644 index 00000000..fb4e311a --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsage.java @@ -0,0 +1,72 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.CUSTOM; +import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; +import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION; +import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL; + +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.LambdaExpressionTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.LambdaExpressionTree; +import com.sun.source.tree.TypeCastTree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Type; +import tech.picnic.errorprone.utils.SourceCode; + +/** + * A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference + * of the form {@code T.class::cast}. + */ +// XXX: Consider folding this logic into the `MethodReferenceUsage` check of the +// `error-prone-experimental` module. +// XXX: This check and its tests are structurally nearly identical to `IsInstanceLambdaUsage`. +// Unless folded into `MethodReferenceUsage`, consider merging the two. +@AutoService(BugChecker.class) +@BugPattern( + summary = "Prefer `Class::cast` method reference over equivalent lambda expression", + link = BUG_PATTERNS_BASE_URL + "ClassCastLambdaUsage", + linkType = CUSTOM, + severity = SUGGESTION, + tags = SIMPLIFICATION) +public final class ClassCastLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher { + private static final long serialVersionUID = 1L; + + /** Instantiates a new {@link ClassCastLambdaUsage} instance. */ + public ClassCastLambdaUsage() {} + + @Override + public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) { + if (tree.getParameters().size() != 1 || !(tree.getBody() instanceof TypeCastTree typeCast)) { + return Description.NO_MATCH; + } + + Type type = ASTHelpers.getType(typeCast); + if (type == null || type.isParameterized() || type.isPrimitive()) { + /* + * The method reference syntax does not support casting to parameterized types. Additionally, + * `Class#cast` does not support the same range of type conversions between (boxed) primitive + * types as the cast operator. + */ + // XXX: Depending on the declared type of the value being cast, in some cases we _can_ rewrite + // primitive casts. Add support for this. + return Description.NO_MATCH; + } + + VariableTree param = Iterables.getOnlyElement(tree.getParameters()); + if (!ASTHelpers.getSymbol(param).equals(ASTHelpers.getSymbol(typeCast.getExpression()))) { + return Description.NO_MATCH; + } + + return describeMatch( + tree, + SuggestedFix.replace( + tree, SourceCode.treeToString(typeCast.getType(), state) + ".class::cast")); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ConstantNaming.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ConstantNaming.java new file mode 100644 index 00000000..73522b7c --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ConstantNaming.java @@ -0,0 +1,125 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.CUSTOM; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.BugPattern.StandardTags.STYLE; +import static com.google.errorprone.matchers.Matchers.allOf; +import static com.google.errorprone.matchers.Matchers.hasModifier; +import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.errorprone.BugPattern; +import com.google.errorprone.ErrorProneFlags; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreeScanner; +import java.util.Locale; +import java.util.regex.Pattern; +import javax.inject.Inject; +import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; +import tech.picnic.errorprone.utils.Flags; + +/** + * A {@link BugChecker} that flags static constants that do not follow the upper snake case naming + * convention. + */ +@AutoService(BugChecker.class) +@BugPattern( + summary = "Constant variables should adhere to the `UPPER_SNAKE_CASE` naming convention", + link = BUG_PATTERNS_BASE_URL + "ConstantNaming", + linkType = CUSTOM, + severity = WARNING, + tags = STYLE) +@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */) +public final class ConstantNaming extends BugChecker implements VariableTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Matcher IS_CONSTANT = + allOf(hasModifier(Modifier.STATIC), hasModifier(Modifier.FINAL)); + private static final Matcher IS_PRIVATE = hasModifier(Modifier.PRIVATE); + private static final Pattern SNAKE_CASE = Pattern.compile("([a-z])([A-Z])"); + private static final ImmutableSet DEFAULT_EXEMPTED_NAMES = + ImmutableSet.of("serialVersionUID"); + + /** + * Flag using which constant names that must not be flagged (in addition to those defined by + * {@link #DEFAULT_EXEMPTED_NAMES}) can be specified. + */ + private static final String ADDITIONAL_EXEMPTED_NAMES_FLAG = + "CanonicalConstantNaming:ExemptedNames"; + + private final ImmutableSet exemptedNames; + + /** Instantiates a default {@link ConstantNaming} instance. */ + public ConstantNaming() { + this(ErrorProneFlags.empty()); + } + + /** + * Instantiates a customized {@link ConstantNaming}. + * + * @param flags Any provided command line flags. + */ + @Inject + ConstantNaming(ErrorProneFlags flags) { + exemptedNames = + Sets.union(DEFAULT_EXEMPTED_NAMES, Flags.getSet(flags, ADDITIONAL_EXEMPTED_NAMES_FLAG)) + .immutableCopy(); + } + + @Override + public Description matchVariable(VariableTree tree, VisitorState state) { + String variableName = tree.getName().toString(); + if (!IS_CONSTANT.matches(tree, state) || exemptedNames.contains(variableName)) { + return Description.NO_MATCH; + } + + String replacement = toUpperSnakeCase(variableName); + if (replacement.equals(variableName)) { + return Description.NO_MATCH; + } + + Description.Builder description = buildDescription(tree); + if (!IS_PRIVATE.matches(tree, state)) { + description.setMessage( + "%s; consider renaming to '%s', though note that this is not a private constant" + .formatted(message(), replacement)); + } else if (isVariableNameInUse(replacement, state)) { + description.setMessage( + "%s; consider renaming to '%s', though note that a variable with this name is already declared" + .formatted(message(), replacement)); + } else { + description.addFix(SuggestedFixes.renameVariable(tree, replacement, state)); + } + + return description.build(); + } + + private static String toUpperSnakeCase(String variableName) { + return SNAKE_CASE.matcher(variableName).replaceAll("$1_$2").toUpperCase(Locale.ROOT); + } + + private static boolean isVariableNameInUse(String name, VisitorState state) { + return Boolean.TRUE.equals( + new TreeScanner() { + @Override + public Boolean visitVariable(VariableTree tree, @Nullable Void unused) { + return ASTHelpers.getSymbol(tree).getSimpleName().contentEquals(name) + || super.visitVariable(tree, null); + } + + @Override + public Boolean reduce(Boolean r1, Boolean r2) { + return Boolean.TRUE.equals(r1) || Boolean.TRUE.equals(r2); + } + }.scan(state.getPath().getCompilationUnit(), null)); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EmptyMethod.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EmptyMethod.java index 306a27c5..b88eb2e6 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EmptyMethod.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/EmptyMethod.java @@ -62,7 +62,7 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher { } private static boolean isInPossibleTestHelperClass(VisitorState state) { - return Optional.ofNullable(ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class)) + return Optional.ofNullable(state.findEnclosing(ClassTree.class)) .map(ClassTree::getSimpleName) .filter(name -> name.toString().contains("Test")) .isPresent(); diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/FormatStringConcatenation.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/FormatStringConcatenation.java index 744c6e73..e80d6961 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/FormatStringConcatenation.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/FormatStringConcatenation.java @@ -36,6 +36,7 @@ import java.util.Formatter; import java.util.List; import java.util.Optional; import org.jspecify.annotations.Nullable; +import tech.picnic.errorprone.utils.MoreASTHelpers; import tech.picnic.errorprone.utils.SourceCode; /** @@ -203,14 +204,10 @@ public final class FormatStringConcatenation extends BugChecker ExpressionTree argument = ASTHelpers.stripParentheses(arguments.get(argPosition)); return argument instanceof BinaryTree - && isStringTyped(argument, state) + && MoreASTHelpers.isStringTyped(argument, state) && ASTHelpers.constValue(argument, String.class) == null; } - private static boolean isStringTyped(ExpressionTree tree, VisitorState state) { - return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state); - } - private static class ReplacementArgumentsConstructor extends SimpleTreeVisitor<@Nullable Void, VisitorState> { private final StringBuilder formatString = new StringBuilder(); @@ -223,7 +220,7 @@ public final class FormatStringConcatenation extends BugChecker @Override public @Nullable Void visitBinary(BinaryTree tree, VisitorState state) { - if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) { + if (tree.getKind() == Kind.PLUS && MoreASTHelpers.isStringTyped(tree, state)) { tree.getLeftOperand().accept(this, state); tree.getRightOperand().accept(this, state); } else { diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java index 65c37c5c..14464da9 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IdentityConversion.java @@ -39,6 +39,7 @@ import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; +import java.time.Instant; import java.util.Arrays; import java.util.List; import tech.picnic.errorprone.utils.SourceCode; @@ -51,6 +52,7 @@ import tech.picnic.errorprone.utils.SourceCode; // is effectively the identity operation. // XXX: Also flag nullary instance method invocations that represent an identity conversion, such as // `Boolean#booleanValue()`, `Byte#byteValue()` and friends. +// XXX: Also flag redundant round-trip conversions such as `path.toFile().toPath()`. @AutoService(BugChecker.class) @BugPattern( summary = "Avoid or clarify identity conversions", @@ -83,6 +85,7 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca ImmutableSetMultimap.class.getCanonicalName(), ImmutableTable.class.getCanonicalName()) .named("copyOf"), + staticMethod().onClass(Instant.class.getCanonicalName()).namedAnyOf("from"), staticMethod().onClass(Matchers.class.getCanonicalName()).namedAnyOf("allOf", "anyOf"), staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"), staticMethod() diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsage.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsage.java index 2fa02a75..62bd72da 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsage.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsage.java @@ -25,6 +25,8 @@ import tech.picnic.errorprone.utils.SourceCode; */ // XXX: Consider folding this logic into the `MethodReferenceUsage` check of the // `error-prone-experimental` module. +// XXX: This check and its tests are structurally nearly identical to `ClassCastLambdaUsage`. Unless +// folded into `MethodReferenceUsage`, consider merging the two. @AutoService(BugChecker.class) @BugPattern( summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression", diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java index 8cfd59cd..372adebb 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitClassModifiers.java @@ -16,7 +16,7 @@ import static tech.picnic.errorprone.utils.MoreJUnitMatchers.TEST_METHOD; import static tech.picnic.errorprone.utils.MoreMatchers.hasMetaAnnotation; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; @@ -70,7 +70,7 @@ public final class JUnitClassModifiers extends BugChecker implements ClassTreeMa SuggestedFixes.removeModifiers( tree.getModifiers(), state, - ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC)) + Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC)) .ifPresent(fixBuilder::merge); if (!HAS_SPRING_CONFIGURATION_ANNOTATION.matches(tree, state)) { diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitValueSource.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitValueSource.java index 6327e392..5a873263 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitValueSource.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/JUnitValueSource.java @@ -232,7 +232,7 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc @Override public @Nullable Void visitReturn(ReturnTree node, @Nullable Void unused) { returnExpressions.add(node.getExpression()); - return super.visitReturn(node, unused); + return super.visitReturn(node, null); } @Override diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListing.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListing.java index e4812e17..a788c699 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListing.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListing.java @@ -14,6 +14,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.Comparators; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import com.google.errorprone.BugPattern; import com.google.errorprone.ErrorProneFlags; import com.google.errorprone.VisitorState; @@ -34,10 +35,8 @@ import com.sun.source.tree.Tree; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; import org.jspecify.annotations.Nullable; @@ -163,7 +162,12 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker /* For now we don't force sorting on numeric types. */ return Stream.of( - symtab.annotationType, symtab.classType, symtab.enumSym.type, symtab.stringType) + symtab.annotationType, + symtab.booleanType, + symtab.charType, + symtab.classType, + symtab.enumSym.type, + symtab.stringType) .anyMatch(t -> ASTHelpers.isSubtype(elemType, t, state)); } @@ -192,7 +196,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker @Override public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) { nodes.add(ImmutableList.of(node.getName().toString())); - return super.visitIdentifier(node, unused); + return super.visitIdentifier(node, null); } @Override @@ -203,13 +207,13 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker ? STRING_ARGUMENT_SPLITTER.splitToStream(str).collect(toImmutableList()) : ImmutableList.of(String.valueOf(value))); - return super.visitLiteral(node, unused); + return super.visitLiteral(node, null); } @Override public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) { nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString())); - return super.visitPrimitiveType(node, unused); + return super.visitPrimitiveType(node, null); } }.scan(array, null); @@ -225,10 +229,8 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker excludedAnnotations(flags)); } - private static ImmutableList excludedAnnotations(ErrorProneFlags flags) { - Set exclusions = new HashSet<>(); - exclusions.addAll(Flags.getList(flags, EXCLUDED_ANNOTATIONS_FLAG)); - exclusions.addAll(BLACKLISTED_ANNOTATIONS); - return ImmutableList.copyOf(exclusions); + private static ImmutableSet excludedAnnotations(ErrorProneFlags flags) { + return Sets.union(BLACKLISTED_ANNOTATIONS, Flags.getSet(flags, EXCLUDED_ANNOTATIONS_FLAG)) + .immutableCopy(); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java index adbd88f7..eba7253b 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/NonStaticImport.java @@ -210,7 +210,7 @@ public final class NonStaticImport extends BugChecker implements CompilationUnit } } - return super.visitIdentifier(node, unused); + return super.visitIdentifier(node, null); } }.scan(tree, null); } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGet.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGet.java new file mode 100644 index 00000000..692ee60e --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGet.java @@ -0,0 +1,123 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.errorprone.BugPattern.LinkType.NONE; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.staticMethod; +import static java.util.stream.Collectors.joining; + +import com.google.auto.service.AutoService; +import com.google.common.collect.Iterables; +import com.google.errorprone.BugPattern; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.refaster.Refaster; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import java.util.Optional; +import java.util.function.Supplier; +import tech.picnic.errorprone.refaster.matchers.RequiresComputation; +import tech.picnic.errorprone.utils.SourceCode; + +/** + * A {@link BugChecker} that flags arguments to {@link Optional#orElse(Object)} that should be + * deferred using {@link Optional#orElseGet(Supplier)}. + * + *

The suggested fix assumes that the argument to {@code orElse} does not have side effects. If + * it does, the suggested fix changes the program's semantics. Such fragile code must instead be + * refactored such that the side-effectful code does not appear accidental. + */ +// XXX: This rule may introduce a compilation error: the `value` expression may reference a +// non-effectively final variable, which is not allowed in the replacement lambda expression. +// Review whether a `@Matcher` can be used to avoid this. +// XXX: Once the `MethodReferenceUsageCheck` bug checker becomes generally usable, consider leaving +// the method reference cleanup to that check, and express the remainder of the logic in this class +// using a Refaster template, i.c.w. a `@NotMatches(RequiresComputation.class)` constraint. +@AutoService(BugChecker.class) +@BugPattern( + summary = + """ + Prefer `Optional#orElseGet` over `Optional#orElse` if the fallback requires additional \ + computation""", + linkType = NONE, + severity = WARNING, + tags = PERFORMANCE) +public final class OptionalOrElseGet extends BugChecker implements MethodInvocationTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Matcher REQUIRES_COMPUTATION = new RequiresComputation(); + private static final Matcher OPTIONAL_OR_ELSE_METHOD = + instanceMethod().onExactClass(Optional.class.getCanonicalName()).namedAnyOf("orElse"); + // XXX: Also exclude invocations of `@Placeholder`-annotated methods. + private static final Matcher REFASTER_METHOD = + staticMethod().onClass(Refaster.class.getCanonicalName()); + + /** Instantiates a new {@link OptionalOrElseGet} instance. */ + public OptionalOrElseGet() {} + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + if (!OPTIONAL_OR_ELSE_METHOD.matches(tree, state)) { + return Description.NO_MATCH; + } + + ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments()); + if (!REQUIRES_COMPUTATION.matches(argument, state) + || REFASTER_METHOD.matches(argument, state)) { + return Description.NO_MATCH; + } + + /* + * We have a match. Construct the method reference or lambda expression to be passed to the + * replacement `#orElseGet` invocation. + */ + String newArgument = + tryMethodReferenceConversion(argument, state) + .orElseGet(() -> "() -> " + SourceCode.treeToString(argument, state)); + + /* Construct the suggested fix, replacing the method invocation and its argument. */ + SuggestedFix fix = + SuggestedFix.builder() + .merge(SuggestedFixes.renameMethodInvocation(tree, "orElseGet", state)) + .replace(argument, newArgument) + .build(); + + return describeMatch(tree, fix); + } + + /** Returns the nullary method reference matching the given expression, if any. */ + private static Optional tryMethodReferenceConversion( + ExpressionTree tree, VisitorState state) { + if (!(tree instanceof MethodInvocationTree methodInvocation)) { + return Optional.empty(); + } + + if (!methodInvocation.getArguments().isEmpty()) { + return Optional.empty(); + } + + if (!(methodInvocation.getMethodSelect() instanceof MemberSelectTree memberSelect)) { + return Optional.empty(); + } + + if (REQUIRES_COMPUTATION.matches(memberSelect.getExpression(), state)) { + return Optional.empty(); + } + + return Optional.of( + SourceCode.treeToString(memberSelect.getExpression(), state) + + "::" + + (methodInvocation.getTypeArguments().isEmpty() + ? "" + : methodInvocation.getTypeArguments().stream() + .map(arg -> SourceCode.treeToString(arg, state)) + .collect(joining(",", "<", ">"))) + + memberSelect.getIdentifier()); + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java index bc6dadbd..689996a0 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RedundantStringConversion.java @@ -378,11 +378,11 @@ public final class RedundantStringConversion extends BugChecker private static Matcher createConversionMethodMatcher( ErrorProneFlags flags) { - // XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas. - // For this class methods accepting more than one argument are not valid, but still: not nice. + // XXX: `Flags#getSet` splits by comma, but method signatures may also contain commas. For this + // class methods accepting more than one argument are not valid, but still: not nice. return anyOf( WELL_KNOWN_STRING_CONVERSION_METHODS, new MethodMatcherFactory() - .create(Flags.getList(flags, EXTRA_STRING_CONVERSION_METHODS_FLAG))); + .create(Flags.getSet(flags, EXTRA_STRING_CONVERSION_METHODS_FLAG))); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RequestParamType.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RequestParamType.java index ee736da1..3d2aef0c 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RequestParamType.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/RequestParamType.java @@ -15,8 +15,8 @@ import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.BugPattern; import com.google.errorprone.ErrorProneFlags; import com.google.errorprone.VisitorState; @@ -74,10 +74,10 @@ public final class RequestParamType extends BugChecker implements VariableTreeMa return allOf( annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")), anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)), - not(isSubtypeOfAny(Flags.getList(flags, SUPPORTED_CUSTOM_TYPES_FLAG)))); + not(isSubtypeOfAny(Flags.getSet(flags, SUPPORTED_CUSTOM_TYPES_FLAG)))); } - private static Matcher isSubtypeOfAny(ImmutableList inclusions) { + private static Matcher isSubtypeOfAny(ImmutableSet inclusions) { return anyOf( inclusions.stream() .map(inclusion -> isSubtypeOf(Suppliers.typeFromString(inclusion))) diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclaration.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclaration.java new file mode 100644 index 00000000..84358607 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclaration.java @@ -0,0 +1,185 @@ +package tech.picnic.errorprone.bugpatterns; + +import static com.google.common.base.Verify.verify; +import static com.google.errorprone.BugPattern.LinkType.CUSTOM; +import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; +import static com.google.errorprone.BugPattern.StandardTags.STYLE; +import static com.google.errorprone.matchers.Matchers.allOf; +import static com.google.errorprone.matchers.Matchers.classLiteral; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.Matchers.staticMethod; +import static com.google.errorprone.matchers.Matchers.toType; +import static java.util.Objects.requireNonNull; +import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL; + +import com.google.auto.service.AutoService; +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +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.VariableTreeMatcher; +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.util.ASTHelpers; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.ModifiersTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Symbol; +import java.util.EnumSet; +import javax.inject.Inject; +import javax.lang.model.element.Modifier; +import tech.picnic.errorprone.utils.MoreASTHelpers; + +/** A {@link BugChecker} that flags non-canonical SLF4J logger declarations. */ +@AutoService(BugChecker.class) +@BugPattern( + summary = "SLF4J logger declarations should follow established best-practices", + link = BUG_PATTERNS_BASE_URL + "Slf4jLoggerDeclaration", + linkType = CUSTOM, + severity = WARNING, + tags = STYLE) +@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */) +public final class Slf4jLoggerDeclaration extends BugChecker implements VariableTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Matcher IS_GET_LOGGER = + staticMethod().onDescendantOf("org.slf4j.LoggerFactory").named("getLogger"); + private static final String CANONICAL_STATIC_LOGGER_NAME_FLAG = + "Slf4jLogDeclaration:CanonicalStaticLoggerName"; + private static final String DEFAULT_CANONICAL_LOGGER_NAME = "LOG"; + private static final Matcher IS_STATIC_ENCLOSING_CLASS_REFERENCE = + classLiteral(Slf4jLoggerDeclaration::isEnclosingClassReference); + private static final Matcher IS_DYNAMIC_ENCLOSING_CLASS_REFERENCE = + toType( + MethodInvocationTree.class, + allOf( + instanceMethod().anyClass().named("getClass").withNoParameters(), + Slf4jLoggerDeclaration::getClassReceiverIsEnclosingClassInstance)); + private static final ImmutableSet INSTANCE_DECLARATION_MODIFIERS = + Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.FINAL); + private static final ImmutableSet STATIC_DECLARATION_MODIFIERS = + Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); + + private final String canonicalStaticFieldName; + private final String canonicalInstanceFieldName; + + /** Instantiates a default {@link Slf4jLoggerDeclaration} instance. */ + public Slf4jLoggerDeclaration() { + this(ErrorProneFlags.empty()); + } + + /** + * Instantiates a customized {@link Slf4jLoggerDeclaration}. + * + * @param flags Any provided command line flags. + */ + @Inject + Slf4jLoggerDeclaration(ErrorProneFlags flags) { + canonicalStaticFieldName = + flags.get(CANONICAL_STATIC_LOGGER_NAME_FLAG).orElse(DEFAULT_CANONICAL_LOGGER_NAME); + canonicalInstanceFieldName = + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, canonicalStaticFieldName); + } + + @Override + public Description matchVariable(VariableTree tree, VisitorState state) { + ExpressionTree initializer = tree.getInitializer(); + if (!IS_GET_LOGGER.matches(initializer, state)) { + return Description.NO_MATCH; + } + + ClassTree clazz = getEnclosingClass(state); + ExpressionTree factoryArg = + Iterables.getOnlyElement(((MethodInvocationTree) initializer).getArguments()); + + SuggestedFix.Builder fix = SuggestedFix.builder(); + + if (clazz.getModifiers().getFlags().contains(Modifier.ABSTRACT) + && IS_DYNAMIC_ENCLOSING_CLASS_REFERENCE.matches(factoryArg, state)) { + /* + * While generally we prefer `Logger` declarations to be static and named after their + * enclosing class, we allow one exception: loggers in abstract classes with a name derived + * from `getClass()`. + */ + suggestModifiers(tree, INSTANCE_DECLARATION_MODIFIERS, fix, state); + suggestRename(tree, canonicalInstanceFieldName, fix, state); + } else { + suggestModifiers( + tree, + clazz.getKind() == Kind.INTERFACE ? ImmutableSet.of() : STATIC_DECLARATION_MODIFIERS, + fix, + state); + suggestRename(tree, canonicalStaticFieldName, fix, state); + + if (!MoreASTHelpers.isStringTyped(factoryArg, state) + && !IS_STATIC_ENCLOSING_CLASS_REFERENCE.matches(factoryArg, state)) { + /* + * Loggers with a custom string name are generally "special", but those with a name derived + * from a class other than the one that encloses it are likely in error. + */ + fix.merge(SuggestedFix.replace(factoryArg, clazz.getSimpleName() + ".class")); + } + } + + return fix.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fix.build()); + } + + private static void suggestModifiers( + VariableTree tree, + ImmutableSet modifiers, + SuggestedFix.Builder fixBuilder, + VisitorState state) { + ModifiersTree modifiersTree = + requireNonNull(ASTHelpers.getModifiers(tree), "`VariableTree` must have modifiers"); + SuggestedFixes.addModifiers(tree, modifiersTree, state, modifiers).ifPresent(fixBuilder::merge); + SuggestedFixes.removeModifiers( + modifiersTree, state, Sets.difference(EnumSet.allOf(Modifier.class), modifiers)) + .ifPresent(fixBuilder::merge); + } + + private static void suggestRename( + VariableTree variableTree, String name, SuggestedFix.Builder fixBuilder, VisitorState state) { + if (!variableTree.getName().contentEquals(name)) { + fixBuilder.merge(SuggestedFixes.renameVariable(variableTree, name, state)); + } + } + + private static boolean isEnclosingClassReference(ExpressionTree tree, VisitorState state) { + return ASTHelpers.getSymbol(getEnclosingClass(state)).equals(ASTHelpers.getSymbol(tree)); + } + + private static boolean getClassReceiverIsEnclosingClassInstance( + MethodInvocationTree getClassInvocationTree, VisitorState state) { + ExpressionTree receiver = ASTHelpers.getReceiver(getClassInvocationTree); + if (receiver == null) { + /* + * Method invocations without an explicit receiver either involve static methods (possibly + * statically imported), or instance methods invoked on the enclosing class. As the given + * `getClassInvocationTree` is guaranteed to be a nullary `#getClass()` invocation, the latter + * must be the case. + */ + return true; + } + + Symbol symbol = ASTHelpers.getSymbol(receiver); + return symbol != null + && symbol.asType().tsym.equals(ASTHelpers.getSymbol(getEnclosingClass(state))); + } + + private static ClassTree getEnclosingClass(VisitorState state) { + ClassTree clazz = state.findEnclosing(ClassTree.class); + // XXX: Review whether we should relax this constraint in the face of so-called anonymous + // classes. See + // https://docs.oracle.com/en/java/javase/23/language/implicitly-declared-classes-and-instance-main-methods.html + verify(clazz != null, "Variable not defined inside class"); + return clazz; + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/AssortedRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/AssortedRules.java index 4841481e..a38a823e 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/AssortedRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/AssortedRules.java @@ -1,8 +1,6 @@ package tech.picnic.errorprone.refasterrules; import static com.google.common.base.Preconditions.checkElementIndex; -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Sets.toImmutableEnumSet; import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS; import static java.util.Collections.disjoint; import static java.util.Objects.checkIndex; @@ -70,28 +68,6 @@ final class AssortedRules { } } - /** - * Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link - * ImmutableSet#toImmutableSet()} and produces a more compact object. - * - *

Warning: this rewrite rule is not completely behavior preserving: while the - * original code produces a set that iterates over the elements in encounter order, the - * replacement code iterates over the elements in enum definition order. - */ - // XXX: ^ Consider emitting a comment warning about this fact? - static final class StreamToImmutableEnumSet> { - @BeforeTemplate - ImmutableSet before(Stream stream) { - return stream.collect(toImmutableSet()); - } - - @AfterTemplate - @UseImportPolicy(STATIC_IMPORT_ALWAYS) - ImmutableSet after(Stream stream) { - return stream.collect(toImmutableEnumSet()); - } - } - /** Prefer {@link Iterators#getNext(Iterator, Object)} over more contrived alternatives. */ static final class IteratorGetNextOrDefault { @BeforeTemplate diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BugCheckerRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BugCheckerRules.java index db21ddb7..9ce8236f 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BugCheckerRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BugCheckerRules.java @@ -9,6 +9,7 @@ import com.google.errorprone.refaster.annotation.AfterTemplate; import com.google.errorprone.refaster.annotation.BeforeTemplate; import com.sun.tools.javac.util.Constants; import com.sun.tools.javac.util.Convert; +import javax.lang.model.element.Name; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; /** Refaster rules related to {@link com.google.errorprone.bugpatterns.BugChecker} classes. */ @@ -67,4 +68,22 @@ final class BugCheckerRules { return Constants.format(value); } } + + /** Prefer {@link Name#contentEquals(CharSequence)} over more verbose alternatives. */ + static final class NameContentEquals { + @BeforeTemplate + boolean before(Name name, CharSequence string) { + return name.toString().equals(string.toString()); + } + + @BeforeTemplate + boolean before(Name name, String string) { + return name.toString().equals(string); + } + + @AfterTemplate + boolean after(Name name, CharSequence string) { + return name.contentEquals(string); + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ClassRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ClassRules.java index 2890dd1b..5b0d4780 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ClassRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ClassRules.java @@ -3,6 +3,7 @@ package tech.picnic.errorprone.refasterrules; import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; import com.google.errorprone.refaster.annotation.BeforeTemplate; +import java.util.function.Function; import java.util.function.Predicate; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; @@ -37,7 +38,12 @@ final class ClassRules { } } - /** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */ + /** + * Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require + * naming a variable. + */ + // XXX: Once the `ClassReferenceIsInstancePredicate` rule is dropped, rename this rule to just + // `ClassIsInstancePredicate`. static final class ClassLiteralIsInstancePredicate { @BeforeTemplate Predicate before() { @@ -50,7 +56,11 @@ final class ClassRules { } } - /** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */ + /** + * Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require + * naming a variable. + */ + // XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default. static final class ClassReferenceIsInstancePredicate { @BeforeTemplate Predicate before(Class clazz) { @@ -62,4 +72,21 @@ final class ClassRules { return clazz::isInstance; } } + + /** + * Prefer {@link Class#cast(Object)} method references over lambda expressions that require naming + * a variable. + */ + // XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default. + static final class ClassReferenceCast { + @BeforeTemplate + Function before(Class clazz) { + return o -> clazz.cast(o); + } + + @AfterTemplate + Function after(Class clazz) { + return clazz::cast; + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/CollectionRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/CollectionRules.java index cd341e79..3a02f6fe 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/CollectionRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/CollectionRules.java @@ -8,7 +8,9 @@ import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; import com.google.errorprone.refaster.annotation.AlsoNegation; import com.google.errorprone.refaster.annotation.BeforeTemplate; +import com.google.errorprone.refaster.annotation.NotMatches; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -21,6 +23,7 @@ import java.util.function.Consumer; import java.util.function.IntFunction; import java.util.stream.Stream; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; +import tech.picnic.errorprone.refaster.matchers.IsRefasterAsVarargs; /** Refaster rules related to expressions dealing with (arbitrary) collections. */ // XXX: There are other Guava `Iterables` methods that should not be called if the input is known to @@ -39,7 +42,7 @@ final class CollectionRules { "java:S1155" /* This violation will be rewritten. */, "LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */, "OptionalFirstCollectionElement" /* This is a more specific template. */, - "StreamIsEmpty" /* This is a more specific template. */, + "StreamFindAnyIsEmpty" /* This is a more specific template. */, "key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict" }) boolean before(Collection collection) { @@ -184,6 +187,24 @@ final class CollectionRules { } } + /** Don't unnecessarily call {@link Stream#distinct()} on an already-unique stream of elements. */ + // XXX: This rule assumes that the `Set` relies on `Object#equals`, rather than a custom + // equivalence relation. + // XXX: Expressions that drop or reorder elements from the stream, such as `.filter`, `.skip` and + // `sorted`, can similarly be simplified. Covering all cases is better done using an Error Prone + // check. + static final class SetStream { + @BeforeTemplate + Stream before(Set set) { + return set.stream().distinct(); + } + + @AfterTemplate + Stream after(Set set) { + return set.stream(); + } + } + /** Prefer {@link ArrayList#ArrayList(Collection)} over the Guava alternative. */ @SuppressWarnings( "NonApiType" /* Matching against `List` would unnecessarily constrain the rule. */) @@ -276,6 +297,23 @@ final class CollectionRules { } } + /** Prefer {@link Arrays#asList(Object[])} over more contrived alternatives. */ + // XXX: Consider moving this rule to `ImmutableListRules` and having it suggest + // `ImmutableList#copyOf`. That would retain immutability, at the cost of no longer handling + // `null`s. + static final class ArraysAsList { + // XXX: This expression produces an unmodifiable list, while the alternative doesn't. + @BeforeTemplate + List before(@NotMatches(IsRefasterAsVarargs.class) T[] array) { + return Arrays.stream(array).toList(); + } + + @AfterTemplate + List after(T[] array) { + return Arrays.asList(array); + } + } + /** Prefer calling {@link Collection#toArray()} over more contrived alternatives. */ static final class CollectionToArray { @BeforeTemplate @@ -327,18 +365,20 @@ final class CollectionRules { } } - /** - * Don't call {@link ImmutableCollection#asList()} if {@link ImmutableCollection#iterator()} is - * called on the result; call it directly. - */ - static final class ImmutableCollectionIterator { + /** Prefer {@link Collection#iterator()} over more contrived or less efficient alternatives. */ + static final class CollectionIterator { + @BeforeTemplate + Iterator before(Collection collection) { + return collection.stream().iterator(); + } + @BeforeTemplate Iterator before(ImmutableCollection collection) { return collection.asList().iterator(); } @AfterTemplate - Iterator after(ImmutableCollection collection) { + Iterator after(Collection collection) { return collection.iterator(); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ComparatorRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ComparatorRules.java index c6d537c0..f846de8a 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ComparatorRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ComparatorRules.java @@ -16,13 +16,18 @@ import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; +import com.google.errorprone.refaster.annotation.AlsoNegation; import com.google.errorprone.refaster.annotation.BeforeTemplate; import com.google.errorprone.refaster.annotation.Matches; +import com.google.errorprone.refaster.annotation.MayOptionallyUse; +import com.google.errorprone.refaster.annotation.Placeholder; import com.google.errorprone.refaster.annotation.Repeated; import com.google.errorprone.refaster.annotation.UseImportPolicy; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Optional; import java.util.function.BinaryOperator; import java.util.function.Function; @@ -92,6 +97,24 @@ final class ComparatorRules { } } + /** Don't explicitly compare enums by their ordinal. */ + abstract static class ComparingEnum, T> { + @Placeholder(allowsIdentity = true) + abstract E toEnumFunction(@MayOptionallyUse T value); + + @BeforeTemplate + @SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */) + Comparator before() { + return comparingInt(v -> toEnumFunction(v).ordinal()); + } + + @AfterTemplate + @UseImportPolicy(STATIC_IMPORT_ALWAYS) + Comparator after() { + return comparing(v -> toEnumFunction(v)); + } + } + /** Don't explicitly create {@link Comparator}s unnecessarily. */ static final class ThenComparing> { @BeforeTemplate @@ -222,18 +245,77 @@ final class ComparatorRules { } } + /** Prefer {@link Collections#sort(List)} over more verbose alternatives. */ + static final class CollectionsSort> { + @BeforeTemplate + void before(List collection) { + Collections.sort(collection, naturalOrder()); + } + + @AfterTemplate + void after(List collection) { + Collections.sort(collection); + } + } + + /** Prefer {@link Collections#min(Collection)} over more verbose alternatives. */ + static final class CollectionsMin> { + @BeforeTemplate + T before(Collection collection) { + return Refaster.anyOf( + Collections.min(collection, naturalOrder()), Collections.max(collection, reverseOrder())); + } + + @AfterTemplate + T after(Collection collection) { + return Collections.min(collection); + } + } + /** * Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection * of values. */ - static final class MinOfVarargs { + static final class MinOfArray { @BeforeTemplate - T before(@Repeated T value, Comparator cmp) { + T before(T[] array, Comparator cmp) { + return Arrays.stream(array).min(cmp).orElseThrow(); + } + + @AfterTemplate + T after(T[] array, Comparator cmp) { + return Collections.min(Arrays.asList(array), cmp); + } + } + + /** + * Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection + * of values. + */ + static final class CollectionsMinWithComparator { + @BeforeTemplate + T before(Collection collection, Comparator cmp) { + return collection.stream().min(cmp).orElseThrow(); + } + + @AfterTemplate + T after(Collection collection, Comparator cmp) { + return Collections.min(collection, cmp); + } + } + + /** + * Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection + * of values. + */ + static final class MinOfVarargs { + @BeforeTemplate + T before(@Repeated T value, Comparator cmp) { return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow(); } @AfterTemplate - T after(@Repeated T value, Comparator cmp) { + T after(@Repeated T value, Comparator cmp) { return Collections.min(Arrays.asList(value), cmp); } } @@ -269,7 +351,7 @@ final class ComparatorRules { static final class MinOfPairCustomOrder { @BeforeTemplate @SuppressWarnings("java:S1067" /* The conditional operators are independent. */) - T before(T value1, T value2, Comparator cmp) { + T before(T value1, T value2, Comparator cmp) { return Refaster.anyOf( cmp.compare(value1, value2) <= 0 ? value1 : value2, cmp.compare(value1, value2) > 0 ? value2 : value1, @@ -284,23 +366,69 @@ final class ComparatorRules { } @AfterTemplate - T after(T value1, T value2, Comparator cmp) { + T after(T value1, T value2, Comparator cmp) { return Comparators.min(value1, value2, cmp); } } + /** Prefer {@link Collections#max(Collection)} over more verbose alternatives. */ + static final class CollectionsMax> { + @BeforeTemplate + T before(Collection collection) { + return Refaster.anyOf( + Collections.max(collection, naturalOrder()), Collections.min(collection, reverseOrder())); + } + + @AfterTemplate + T after(Collection collection) { + return Collections.max(collection); + } + } + /** * Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection * of values. */ - static final class MaxOfVarargs { + static final class MaxOfArray { @BeforeTemplate - T before(@Repeated T value, Comparator cmp) { + T before(T[] array, Comparator cmp) { + return Arrays.stream(array).max(cmp).orElseThrow(); + } + + @AfterTemplate + T after(T[] array, Comparator cmp) { + return Collections.max(Arrays.asList(array), cmp); + } + } + + /** + * Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection + * of values. + */ + static final class CollectionsMaxWithComparator { + @BeforeTemplate + T before(Collection collection, Comparator cmp) { + return collection.stream().max(cmp).orElseThrow(); + } + + @AfterTemplate + T after(Collection collection, Comparator cmp) { + return Collections.max(collection, cmp); + } + } + + /** + * Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection + * of values. + */ + static final class MaxOfVarargs { + @BeforeTemplate + T before(@Repeated T value, Comparator cmp) { return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow(); } @AfterTemplate - T after(@Repeated T value, Comparator cmp) { + T after(@Repeated T value, Comparator cmp) { return Collections.max(Arrays.asList(value), cmp); } } @@ -336,7 +464,7 @@ final class ComparatorRules { static final class MaxOfPairCustomOrder { @BeforeTemplate @SuppressWarnings("java:S1067" /* The conditional operators are independent. */) - T before(T value1, T value2, Comparator cmp) { + T before(T value1, T value2, Comparator cmp) { return Refaster.anyOf( cmp.compare(value1, value2) >= 0 ? value1 : value2, cmp.compare(value1, value2) < 0 ? value2 : value1, @@ -351,7 +479,7 @@ final class ComparatorRules { } @AfterTemplate - T after(T value1, T value2, Comparator cmp) { + T after(T value1, T value2, Comparator cmp) { return Comparators.max(value1, value2, cmp); } } @@ -419,4 +547,34 @@ final class ComparatorRules { return maxBy(naturalOrder()); } } + + /** Don't explicitly compare enums by their ordinal. */ + static final class IsLessThan> { + @BeforeTemplate + @SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */) + boolean before(E value1, E value2) { + return value1.ordinal() < value2.ordinal(); + } + + @AfterTemplate + @AlsoNegation + boolean after(E value1, E value2) { + return value1.compareTo(value2) < 0; + } + } + + /** Don't explicitly compare enums by their ordinal. */ + static final class IsLessThanOrEqualTo> { + @BeforeTemplate + @SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */) + boolean before(E value1, E value2) { + return value1.ordinal() <= value2.ordinal(); + } + + @AfterTemplate + @AlsoNegation + boolean after(E value1, E value2) { + return value1.compareTo(value2) <= 0; + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/EqualityRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/EqualityRules.java index e47918d4..dcce6aa0 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/EqualityRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/EqualityRules.java @@ -1,5 +1,6 @@ package tech.picnic.errorprone.refasterrules; +import static java.util.function.Predicate.isEqual; import static java.util.function.Predicate.not; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -19,9 +20,9 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; final class EqualityRules { private EqualityRules() {} - /** Prefer reference-based quality for enums. */ - // Primitive value comparisons are not listed, because Error Prone flags those out of the box. - static final class PrimitiveOrReferenceEquality> { + /** Prefer reference-based equality for enums. */ + // Primitive value comparisons are not matched, because Error Prone flags those out of the box. + static final class EnumReferenceEquality> { /** * Enums can be compared by reference. It is safe to do so even in the face of refactorings, * because if the type is ever converted to a non-enum, then Error-Prone will complain about any @@ -30,8 +31,9 @@ final class EqualityRules { // XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We // work around the issue by selecting the "largest replacements". See the `Refaster` check. @BeforeTemplate + @SuppressWarnings("EnumOrdinal" /* This violation will be rewritten. */) boolean before(T a, T b) { - return Refaster.anyOf(a.equals(b), Objects.equals(a, b)); + return Refaster.anyOf(a.equals(b), Objects.equals(a, b), a.ordinal() == b.ordinal()); } @AfterTemplate @@ -42,6 +44,20 @@ final class EqualityRules { } } + /** Prefer reference-based equality for enums. */ + static final class EnumReferenceEqualityLambda> { + @BeforeTemplate + Predicate before(T e) { + return Refaster.anyOf(isEqual(e), e::equals); + } + + @AfterTemplate + @SuppressWarnings("java:S1698" /* Reference comparison is valid for enums. */) + Predicate after(T e) { + return v -> v == e; + } + } + /** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */ // 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. diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/FileRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/FileRules.java index bc230e0a..4454e34f 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/FileRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/FileRules.java @@ -2,12 +2,18 @@ package tech.picnic.errorprone.refasterrules; import static java.nio.charset.StandardCharsets.UTF_8; +import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; import com.google.errorprone.refaster.annotation.BeforeTemplate; +import com.google.errorprone.refaster.annotation.Repeated; +import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileAttribute; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; /** Refaster rules related to expressions dealing with files. */ @@ -15,6 +21,49 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; final class FileRules { private FileRules() {} + /** Prefer the more idiomatic {@link Path#of(URI)} over {@link Paths#get(URI)}. */ + static final class PathOfUri { + @BeforeTemplate + Path before(URI uri) { + return Paths.get(uri); + } + + @AfterTemplate + Path after(URI uri) { + return Path.of(uri); + } + } + + /** + * Prefer the more idiomatic {@link Path#of(String, String...)} over {@link Paths#get(String, + * String...)}. + */ + static final class PathOfString { + @BeforeTemplate + Path before(String first, @Repeated String more) { + return Paths.get(first, more); + } + + @AfterTemplate + Path after(String first, @Repeated String more) { + return Path.of(first, more); + } + } + + /** Avoid redundant conversions from {@link Path} to {@link File}. */ + // XXX: Review whether a rule such as this one is better handled by the `IdentityConversion` rule. + static final class PathInstance { + @BeforeTemplate + Path before(Path path) { + return path.toFile().toPath(); + } + + @AfterTemplate + Path after(Path path) { + return path; + } + } + /** Prefer {@link Files#readString(Path, Charset)} over more contrived alternatives. */ static final class FilesReadStringWithCharset { @BeforeTemplate @@ -40,4 +89,44 @@ final class FileRules { return Files.readString(path); } } + + /** + * Prefer {@link Files#createTempFile(String, String, FileAttribute[])} over alternatives that + * create files with more liberal permissions. + */ + static final class FilesCreateTempFileToFile { + @BeforeTemplate + @SuppressWarnings({ + "FilesCreateTempFileInCustomDirectoryToFile" /* This is a more specific template. */, + "java:S5443" /* This violation will be rewritten. */, + "key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict" + }) + File before(String prefix, String suffix) throws IOException { + return Refaster.anyOf( + File.createTempFile(prefix, suffix), File.createTempFile(prefix, suffix, null)); + } + + @AfterTemplate + @SuppressWarnings( + "java:S5443" /* On POSIX systems the file will only have user read-write permissions. */) + File after(String prefix, String suffix) throws IOException { + return Files.createTempFile(prefix, suffix).toFile(); + } + } + + /** + * Prefer {@link Files#createTempFile(Path, String, String, FileAttribute[])} over alternatives + * that create files with more liberal permissions. + */ + static final class FilesCreateTempFileInCustomDirectoryToFile { + @BeforeTemplate + File before(File directory, String prefix, String suffix) throws IOException { + return File.createTempFile(prefix, suffix, directory); + } + + @AfterTemplate + File after(File directory, String prefix, String suffix) throws IOException { + return Files.createTempFile(directory.toPath(), prefix, suffix).toFile(); + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRules.java new file mode 100644 index 00000000..b7b40af6 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRules.java @@ -0,0 +1,246 @@ +package tech.picnic.errorprone.refasterrules; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Sets.toImmutableEnumSet; +import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.errorprone.refaster.Refaster; +import com.google.errorprone.refaster.annotation.AfterTemplate; +import com.google.errorprone.refaster.annotation.BeforeTemplate; +import com.google.errorprone.refaster.annotation.Repeated; +import com.google.errorprone.refaster.annotation.UseImportPolicy; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.stream.Stream; +import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; + +/** + * Refaster rules related to expressions dealing with {@code + * com.google.common.collect.ImmutableEnumSet}s. + */ +// XXX: Some of the rules defined here impact iteration order. That's a rather subtle change. Should +// we emit a comment warning about this fact? (This may produce a lot of noise. A bug checker could +// in some cases determine whether iteration order is important.) +// XXX: Consider replacing the `SetsImmutableEnumSet[N]` Refaster rules with a bug checker, such +// that call to `ImmutableSet#of(Object, Object, Object, Object, Object, Object, Object[])` with +// enum-typed values can also be rewritten. +@OnlineDocumentation +final class ImmutableEnumSetRules { + private ImmutableEnumSetRules() {} + + /** + * Prefer {@link Sets#immutableEnumSet(Iterable)} for enum collections to take advantage of the + * internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the + * original code produces a set that iterates over its elements in the same order as the input + * {@link Iterable}, the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSetIterable> { + @BeforeTemplate + ImmutableSet before(Iterable elements) { + return ImmutableSet.copyOf(elements); + } + + @BeforeTemplate + ImmutableSet before(Collection elements) { + return ImmutableSet.copyOf(elements); + } + + @AfterTemplate + ImmutableSet after(Iterable elements) { + return Sets.immutableEnumSet(elements); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Iterable)} for enum collections to take advantage of the + * internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the + * original code produces a set that iterates over its elements in the same order as defined in + * the array, the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSetArraysAsList> { + @BeforeTemplate + ImmutableSet before(T[] elements) { + return ImmutableSet.copyOf(elements); + } + + @AfterTemplate + ImmutableSet after(T[] elements) { + return Sets.immutableEnumSet(Arrays.asList(elements)); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + */ + static final class SetsImmutableEnumSet1> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1) { + return Refaster.anyOf(ImmutableSet.of(e1), ImmutableSet.copyOf(EnumSet.of(e1))); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1) { + return Sets.immutableEnumSet(e1); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the {@link + * ImmutableSet#of} expression produces a set that iterates over its elements in the listed order, + * the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSet2> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1, T e2) { + return Refaster.anyOf(ImmutableSet.of(e1, e2), ImmutableSet.copyOf(EnumSet.of(e1, e2))); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1, T e2) { + return Sets.immutableEnumSet(e1, e2); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the {@link + * ImmutableSet#of} expression produces a set that iterates over its elements in the listed order, + * the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSet3> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1, T e2, T e3) { + return Refaster.anyOf( + ImmutableSet.of(e1, e2, e3), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3))); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1, T e2, T e3) { + return Sets.immutableEnumSet(e1, e2, e3); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the {@link + * ImmutableSet#of} expression produces a set that iterates over its elements in the listed order, + * the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSet4> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1, T e2, T e3, T e4) { + return Refaster.anyOf( + ImmutableSet.of(e1, e2, e3, e4), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3, e4))); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1, T e2, T e3, T e4) { + return Sets.immutableEnumSet(e1, e2, e3, e4); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the {@link + * ImmutableSet#of} expression produces a set that iterates over its elements in the listed order, + * the replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSet5> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1, T e2, T e3, T e4, T e5) { + return Refaster.anyOf( + ImmutableSet.of(e1, e2, e3, e4, e5), ImmutableSet.copyOf(EnumSet.of(e1, e2, e3, e4, e5))); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1, T e2, T e3, T e4, T e5) { + return Sets.immutableEnumSet(e1, e2, e3, e4, e5); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + * + *

Warning: this rule is not completely behavior preserving: while the + * original code produces a set that iterates over its elements in the listed order, the + * replacement code iterates over the elements in enum definition order. + */ + static final class SetsImmutableEnumSet6> { + @BeforeTemplate + ImmutableSet before(T e1, T e2, T e3, T e4, T e5, T e6) { + return ImmutableSet.of(e1, e2, e3, e4, e5, e6); + } + + @AfterTemplate + @SuppressWarnings("unchecked") + ImmutableSet after(T e1, T e2, T e3, T e4, T e5, T e6) { + return Sets.immutableEnumSet(e1, e2, e3, e4, e5, e6); + } + } + + /** + * Prefer {@link Sets#immutableEnumSet(Enum, Enum[])} for enum collections to take advantage of + * the internally used {@link EnumSet}. + */ + static final class SetsImmutableEnumSetVarArgs> { + @BeforeTemplate + @SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */) + ImmutableSet before(T e1, @Repeated T elements) { + return ImmutableSet.copyOf(EnumSet.of(e1, Refaster.asVarargs(elements))); + } + + @AfterTemplate + ImmutableSet after(T e1, @Repeated T elements) { + return Sets.immutableEnumSet(e1, Refaster.asVarargs(elements)); + } + } + + /** + * Use {@link Sets#toImmutableEnumSet()} when possible, as it is more efficient than {@link + * ImmutableSet#toImmutableSet()} and produces a more compact object. + * + *

Warning: this rule is not completely behavior preserving: while the + * original code produces a set that iterates over its elements in encounter order, the + * replacement code iterates over the elements in enum definition order. + */ + static final class StreamToImmutableEnumSet> { + @BeforeTemplate + ImmutableSet before(Stream stream) { + return stream.collect(toImmutableSet()); + } + + @AfterTemplate + @UseImportPolicy(STATIC_IMPORT_ALWAYS) + ImmutableSet after(Stream stream) { + return stream.collect(toImmutableEnumSet()); + } + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/JUnitToAssertJRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/JUnitToAssertJRules.java index 23c3bc16..e7915237 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/JUnitToAssertJRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/JUnitToAssertJRules.java @@ -302,16 +302,22 @@ import tech.picnic.errorprone.refaster.annotation.TypeMigration; final class JUnitToAssertJRules { private JUnitToAssertJRules() {} - static final class ThrowNewAssertionError { + static final class Fail { @BeforeTemplate - void before() { - Assertions.fail(); + T before() { + return Assertions.fail(); } + // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once + // https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically + // importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's + // `fail`. Note that combining Error Prone's `RemoveUnusedImports` and + // `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the + // method to be imported statically if possible; just in a less efficient manner. @AfterTemplate @DoNotCall - void after() { - throw new AssertionError(); + T after() { + return fail(); } } @@ -321,12 +327,7 @@ final class JUnitToAssertJRules { return Assertions.fail(message); } - // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once - // https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically - // importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's - // `fail`. Note that combining Error Prone's `RemoveUnusedImports` and - // `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the - // method to be imported statically if possible; just in a less efficient manner. + // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment. @AfterTemplate T after(String message) { return fail(message); @@ -339,28 +340,24 @@ final class JUnitToAssertJRules { return Assertions.fail(message, throwable); } - // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once - // https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically - // importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's - // `fail`. Note that combining Error Prone's `RemoveUnusedImports` and - // `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the - // method to be imported statically if possible; just in a less efficient manner. + // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment. @AfterTemplate T after(String message, Throwable throwable) { return fail(message, throwable); } } - static final class FailWithThrowable { + static final class FailWithThrowable { @BeforeTemplate - void before(Throwable throwable) { - Assertions.fail(throwable); + T before(Throwable throwable) { + return Assertions.fail(throwable); } + // XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment. @AfterTemplate @DoNotCall - void after(Throwable throwable) { - throw new AssertionError(throwable); + T after(Throwable throwable) { + return fail(throwable); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/MicrometerRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/MicrometerRules.java new file mode 100644 index 00000000..40800c6d --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/MicrometerRules.java @@ -0,0 +1,88 @@ +package tech.picnic.errorprone.refasterrules; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.refaster.Refaster; +import com.google.errorprone.refaster.annotation.AfterTemplate; +import com.google.errorprone.refaster.annotation.BeforeTemplate; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; + +/** Refaster rules related to expressions dealing with Micrometer. */ +// XXX: Consider replacing the `TagsOf[N]` rules with a bug checker, so that various other +// expressions (e.g. those creating other collection types, those passing in tags some other way, or +// those passing in more tags) can be replaced as wel. +@OnlineDocumentation +final class MicrometerRules { + private MicrometerRules() {} + + /** Prefer using {@link Tags} over other immutable collections. */ + static final class TagsOf1 { + @BeforeTemplate + ImmutableCollection before(Tag tag) { + return Refaster.anyOf(ImmutableSet.of(tag), ImmutableList.of(tag)); + } + + @AfterTemplate + Iterable after(Tag tag) { + return Tags.of(tag); + } + } + + /** Prefer using {@link Tags} over other immutable collections. */ + static final class TagsOf2 { + @BeforeTemplate + ImmutableCollection before(Tag tag1, Tag tag2) { + return Refaster.anyOf(ImmutableSet.of(tag1, tag2), ImmutableList.of(tag1, tag2)); + } + + @AfterTemplate + Iterable after(Tag tag1, Tag tag2) { + return Tags.of(tag1, tag2); + } + } + + /** Prefer using {@link Tags} over other immutable collections. */ + static final class TagsOf3 { + @BeforeTemplate + ImmutableCollection before(Tag tag1, Tag tag2, Tag tag3) { + return Refaster.anyOf(ImmutableSet.of(tag1, tag2, tag3), ImmutableList.of(tag1, tag2, tag3)); + } + + @AfterTemplate + Iterable after(Tag tag1, Tag tag2, Tag tag3) { + return Tags.of(tag1, tag2, tag3); + } + } + + /** Prefer using {@link Tags} over other immutable collections. */ + static final class TagsOf4 { + @BeforeTemplate + ImmutableCollection before(Tag tag1, Tag tag2, Tag tag3, Tag tag4) { + return Refaster.anyOf( + ImmutableSet.of(tag1, tag2, tag3, tag4), ImmutableList.of(tag1, tag2, tag3, tag4)); + } + + @AfterTemplate + Iterable after(Tag tag1, Tag tag2, Tag tag3, Tag tag4) { + return Tags.of(tag1, tag2, tag3, tag4); + } + } + + /** Prefer using {@link Tags} over other immutable collections. */ + static final class TagsOf5 { + @BeforeTemplate + ImmutableCollection before(Tag tag1, Tag tag2, Tag tag3, Tag tag4, Tag tag5) { + return Refaster.anyOf( + ImmutableSet.of(tag1, tag2, tag3, tag4, tag5), + ImmutableList.of(tag1, tag2, tag3, tag4, tag5)); + } + + @AfterTemplate + Iterable after(Tag tag1, Tag tag2, Tag tag3, Tag tag4, Tag tag5) { + return Tags.of(tag1, tag2, tag3, tag4, tag5); + } + } +} diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/OptionalRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/OptionalRules.java index 1491b18e..cc8928aa 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/OptionalRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/OptionalRules.java @@ -20,13 +20,26 @@ import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; -import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation; +import tech.picnic.errorprone.refaster.matchers.RequiresComputation; /** Refaster rules related to expressions dealing with {@link Optional}s. */ @OnlineDocumentation final class OptionalRules { private OptionalRules() {} + /** Prefer {@link Optional#empty()} over the more contrived alternative. */ + static final class OptionalEmpty { + @BeforeTemplate + Optional before() { + return Optional.ofNullable(null); + } + + @AfterTemplate + Optional after() { + return Optional.empty(); + } + } + static final class OptionalOfNullable { // XXX: Refaster should be smart enough to also rewrite occurrences in which there are // parentheses around the null check, but that's currently not the case. Try to fix that. @@ -242,24 +255,21 @@ final class OptionalRules { } /** - * Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the - * fallback value is not the result of a trivial computation. + * Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the + * fallback value does not require non-trivial computation. */ - // XXX: This rule may introduce a compilation error: the `value` expression may reference a - // non-effectively final variable, which is not allowed in the replacement lambda expression. - // Review whether a `@Matcher` can be used to avoid this. - // XXX: Once `MethodReferenceUsage` is "production ready", replace - // `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and - // reimplement the matcher accordingly). - static final class OptionalOrElseGet { + // XXX: This rule is the counterpart to the `OptionalOrElseGet` bug checker. Once the + // `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be + // replaced with a Refaster rule. + static final class OptionalOrElse { @BeforeTemplate - T before(Optional optional, @NotMatches(IsLikelyTrivialComputation.class) T value) { - return optional.orElse(value); + T before(Optional optional, @NotMatches(RequiresComputation.class) T value) { + return optional.orElseGet(() -> value); } @AfterTemplate T after(Optional optional, T value) { - return optional.orElseGet(() -> value); + return optional.orElse(value); } } @@ -269,6 +279,9 @@ final class OptionalRules { */ // XXX: Do we need the `.filter(Optional::isPresent)`? If it's absent the caller probably assumed // that the values are present. (If we drop it, we should rewrite vacuous filter steps.) + // XXX: The rewritten `filter`/`map` expression may be more performant than its replacement. See + // https://github.com/palantir/gradle-baseline/pull/2946. (There are plans to pair Refaster rules + // with JMH benchmarks; this would be a great use case.) static final class StreamFlatMapOptional { @BeforeTemplate Stream before(Stream> stream) { @@ -360,7 +373,12 @@ final class OptionalRules { /** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */ static final class OptionalOrOtherOptional { @BeforeTemplate - @SuppressWarnings("NestedOptionals" /* Auto-fix for the `NestedOptionals` check. */) + @SuppressWarnings({ + "LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */, + "NestedOptionals" /* This violation will be rewritten. */, + "OptionalOrElse" /* Parameters represent expressions that may require computation. */, + "key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict" + }) Optional before(Optional optional1, Optional optional2) { // XXX: Note that rewriting the first and third variant will change the code's behavior if // `optional2` has side-effects. @@ -380,15 +398,16 @@ final class OptionalRules { } } - /** - * Avoid unnecessary operations on an {@link Optional} that ultimately result in that very same - * {@link Optional}. - */ + /** Don't unnecessarily transform an {@link Optional} to an equivalent instance. */ static final class OptionalIdentity { @BeforeTemplate + @SuppressWarnings("NestedOptionals") Optional before(Optional optional, Comparator comparator) { return Refaster.anyOf( optional.or(Refaster.anyOf(() -> Optional.empty(), Optional::empty)), + optional + .map(Optional::of) + .orElseGet(Refaster.anyOf(() -> Optional.empty(), Optional::empty)), optional.stream().findFirst(), optional.stream().findAny(), optional.stream().min(comparator), @@ -442,9 +461,7 @@ final class OptionalRules { static final class OptionalStream { @BeforeTemplate Stream before(Optional optional) { - return Refaster.anyOf( - optional.map(Stream::of).orElse(Stream.empty()), - optional.map(Stream::of).orElseGet(Stream::empty)); + return optional.map(Stream::of).orElseGet(Stream::empty); } @AfterTemplate diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/PrimitiveRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/PrimitiveRules.java index f39d4e99..b034ec73 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/PrimitiveRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/PrimitiveRules.java @@ -8,6 +8,8 @@ import com.google.common.primitives.Floats; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.common.primitives.Shorts; +import com.google.common.primitives.UnsignedInts; +import com.google.common.primitives.UnsignedLongs; import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; import com.google.errorprone.refaster.annotation.AlsoNegation; @@ -76,6 +78,8 @@ final class PrimitiveRules { } /** Prefer {@link Math#toIntExact(long)} over the Guava alternative. */ + // XXX: This rule changes the exception possibly thrown from `IllegalArgumentException` to + // `ArithmeticException`. static final class LongToIntExact { @BeforeTemplate int before(long l) { @@ -192,97 +196,6 @@ final class PrimitiveRules { } } - /** Prefer {@link Boolean#compare(boolean, boolean)} over the Guava alternative. */ - static final class BooleanCompare { - @BeforeTemplate - int before(boolean a, boolean b) { - return Booleans.compare(a, b); - } - - @AfterTemplate - int after(boolean a, boolean b) { - return Boolean.compare(a, b); - } - } - - /** Prefer {@link Character#compare(char, char)} over the Guava alternative. */ - static final class CharacterCompare { - @BeforeTemplate - int before(char a, char b) { - return Chars.compare(a, b); - } - - @AfterTemplate - int after(char a, char b) { - return Character.compare(a, b); - } - } - - /** Prefer {@link Short#compare(short, short)} over the Guava alternative. */ - static final class ShortCompare { - @BeforeTemplate - int before(short a, short b) { - return Shorts.compare(a, b); - } - - @AfterTemplate - int after(short a, short b) { - return Short.compare(a, b); - } - } - - /** Prefer {@link Integer#compare(int, int)} over the Guava alternative. */ - static final class IntegerCompare { - @BeforeTemplate - int before(int a, int b) { - return Ints.compare(a, b); - } - - @AfterTemplate - int after(int a, int b) { - return Integer.compare(a, b); - } - } - - /** Prefer {@link Long#compare(long, long)} over the Guava alternative. */ - static final class LongCompare { - @BeforeTemplate - int before(long a, long b) { - return Longs.compare(a, b); - } - - @AfterTemplate - int after(long a, long b) { - return Long.compare(a, b); - } - } - - /** Prefer {@link Float#compare(float, float)} over the Guava alternative. */ - static final class FloatCompare { - @BeforeTemplate - int before(float a, float b) { - return Floats.compare(a, b); - } - - @AfterTemplate - int after(float a, float b) { - return Float.compare(a, b); - } - } - - /** Prefer {@link Double#compare(double, double)} over the Guava alternative. */ - static final class DoubleCompare { - @BeforeTemplate - int before(double a, double b) { - return Doubles.compare(a, b); - } - - @AfterTemplate - int after(double a, double b) { - return Double.compare(a, b); - } - } - /** Prefer {@link Character#BYTES} over the Guava alternative. */ static final class CharacterBytes { @BeforeTemplate @@ -442,4 +355,205 @@ final class PrimitiveRules { return Long.signum(l) == -1; } } + + /** Prefer JDK's {@link Integer#compareUnsigned(int, int)} over third-party alternatives. */ + static final class IntegerCompareUnsigned { + @BeforeTemplate + int before(int x, int y) { + return UnsignedInts.compare(x, y); + } + + @AfterTemplate + int after(int x, int y) { + return Integer.compareUnsigned(x, y); + } + } + + /** Prefer JDK's {@link Long#compareUnsigned(long, long)} over third-party alternatives. */ + static final class LongCompareUnsigned { + @BeforeTemplate + long before(long x, long y) { + return UnsignedLongs.compare(x, y); + } + + @AfterTemplate + long after(long x, long y) { + return Long.compareUnsigned(x, y); + } + } + + /** Prefer JDK's {@link Integer#divideUnsigned(int, int)} over third-party alternatives. */ + static final class IntegerDivideUnsigned { + @BeforeTemplate + int before(int x, int y) { + return UnsignedInts.divide(x, y); + } + + @AfterTemplate + int after(int x, int y) { + return Integer.divideUnsigned(x, y); + } + } + + /** Prefer JDK's {@link Long#divideUnsigned(long, long)} over third-party alternatives. */ + static final class LongDivideUnsigned { + @BeforeTemplate + long before(long x, long y) { + return UnsignedLongs.divide(x, y); + } + + @AfterTemplate + long after(long x, long y) { + return Long.divideUnsigned(x, y); + } + } + + /** Prefer JDK's {@link Integer#remainderUnsigned(int, int)} over third-party alternatives. */ + static final class IntegerRemainderUnsigned { + @BeforeTemplate + int before(int x, int y) { + return UnsignedInts.remainder(x, y); + } + + @AfterTemplate + int after(int x, int y) { + return Integer.remainderUnsigned(x, y); + } + } + + /** Prefer JDK's {@link Long#remainderUnsigned(long, long)} over third-party alternatives. */ + static final class LongRemainderUnsigned { + @BeforeTemplate + long before(long x, long y) { + return UnsignedLongs.remainder(x, y); + } + + @AfterTemplate + long after(long x, long y) { + return Long.remainderUnsigned(x, y); + } + } + + /** + * Prefer JDK's {@link Integer#parseUnsignedInt(String)} over third-party or more verbose + * alternatives. + */ + static final class IntegerParseUnsignedInt { + @BeforeTemplate + int before(String string) { + return Refaster.anyOf( + UnsignedInts.parseUnsignedInt(string), Integer.parseUnsignedInt(string, 10)); + } + + @AfterTemplate + int after(String string) { + return Integer.parseUnsignedInt(string); + } + } + + /** + * Prefer JDK's {@link Long#parseUnsignedLong(String)} over third-party or more verbose + * alternatives. + */ + static final class LongParseUnsignedLong { + @BeforeTemplate + long before(String string) { + return Refaster.anyOf( + UnsignedLongs.parseUnsignedLong(string), Long.parseUnsignedLong(string, 10)); + } + + @AfterTemplate + long after(String string) { + return Long.parseUnsignedLong(string); + } + } + + /** Prefer JDK's {@link Integer#parseUnsignedInt(String, int)} over third-party alternatives. */ + static final class IntegerParseUnsignedIntWithRadix { + @BeforeTemplate + int before(String string, int radix) { + return UnsignedInts.parseUnsignedInt(string, radix); + } + + @AfterTemplate + int after(String string, int radix) { + return Integer.parseUnsignedInt(string, radix); + } + } + + /** Prefer JDK's {@link Long#parseUnsignedLong(String, int)} over third-party alternatives. */ + static final class LongParseUnsignedLongWithRadix { + @BeforeTemplate + long before(String string, int radix) { + return UnsignedLongs.parseUnsignedLong(string, radix); + } + + @AfterTemplate + long after(String string, int radix) { + return Long.parseUnsignedLong(string, radix); + } + } + + /** + * Prefer JDK's {@link Integer#toUnsignedString(int)} over third-party or more verbose + * alternatives. + */ + static final class IntegerToUnsignedString { + @BeforeTemplate + String before(int i) { + return Refaster.anyOf(UnsignedInts.toString(i), Integer.toUnsignedString(i, 10)); + } + + @AfterTemplate + String after(int i) { + return Integer.toUnsignedString(i); + } + } + + /** + * Prefer JDK's {@link Long#toUnsignedString(long)} over third-party or more verbose alternatives. + */ + static final class LongToUnsignedString { + @BeforeTemplate + String before(long i) { + return Refaster.anyOf(UnsignedLongs.toString(i), Long.toUnsignedString(i, 10)); + } + + @AfterTemplate + String after(long i) { + return Long.toUnsignedString(i); + } + } + + /** + * Prefer JDK's {@link Integer#toUnsignedString(int,int)} over third-party or more verbose + * alternatives. + */ + static final class IntegerToUnsignedStringWithRadix { + @BeforeTemplate + String before(int i, int radix) { + return UnsignedInts.toString(i, radix); + } + + @AfterTemplate + String after(int i, int radix) { + return Integer.toUnsignedString(i, radix); + } + } + + /** + * Prefer JDK's {@link Long#toUnsignedString(long,int)} over third-party or more verbose + * alternatives. + */ + static final class LongToUnsignedStringWithRadix { + @BeforeTemplate + String before(long i, int radix) { + return UnsignedLongs.toString(i, radix); + } + + @AfterTemplate + String after(long i, int radix) { + return Long.toUnsignedString(i, radix); + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java index 57cb55a6..404f7830 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/ReactorRules.java @@ -3,7 +3,6 @@ package tech.picnic.errorprone.refasterrules; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.MoreCollectors.toOptional; -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS; import static java.util.Comparator.naturalOrder; import static java.util.Comparator.reverseOrder; @@ -14,6 +13,7 @@ import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static reactor.function.TupleUtils.function; +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -41,6 +42,7 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collector; +import java.util.stream.Stream; import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -52,7 +54,6 @@ import reactor.util.context.Context; import reactor.util.function.Tuple2; import tech.picnic.errorprone.refaster.annotation.Description; import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; -import tech.picnic.errorprone.refaster.annotation.Severity; import tech.picnic.errorprone.refaster.matchers.IsEmpty; import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation; import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException; @@ -380,30 +381,23 @@ final class ReactorRules { } /** - * Prefer {@link Flux#take(long, boolean)} over {@link Flux#take(long)}. + * Prefer {@link Flux#take(long)} over {@link Flux#take(long, boolean)} where relevant. * *

In Reactor versions prior to 3.5.0, {@code Flux#take(long)} makes an unbounded request - * upstream, and is equivalent to {@code Flux#take(long, false)}. In 3.5.0, the behavior of {@code - * Flux#take(long)} will change to that of {@code Flux#take(long, true)}. - * - *

The intent with this Refaster rule is to get the new behavior before upgrading to Reactor - * 3.5.0. + * upstream, and is equivalent to {@code Flux#take(long, false)}. From version 3.5.0 onwards, the + * behavior of {@code Flux#take(long)} instead matches {@code Flux#take(long, true)}. */ - // XXX: Drop this rule some time after upgrading to Reactor 3.6.0, or introduce a way to apply - // this rule only when an older version of Reactor is on the classpath. - // XXX: Once Reactor 3.6.0 is out, introduce a rule that rewrites code in the opposite direction. @Description( - "Prior to Reactor 3.5.0, `take(n)` requests and unbounded number of elements upstream.") - @Severity(WARNING) + "From Reactor 3.5.0 onwards, `take(n)` no longer requests an unbounded number of elements upstream.") static final class FluxTake { @BeforeTemplate Flux before(Flux flux, long n) { - return flux.take(n); + return flux.take(n, /* limitRequest= */ true); } @AfterTemplate Flux after(Flux flux, long n) { - return flux.take(n, /* limitRequest= */ true); + return flux.take(n); } } @@ -489,15 +483,20 @@ final class ReactorRules { } /** Prefer {@link Flux#just(Object)} over more contrived alternatives. */ - static final class FluxJust { + static final class FluxJust { @BeforeTemplate - Flux before(int start) { - return Flux.range(start, 1); + Flux before(int value) { + return Flux.range(value, 1); + } + + @BeforeTemplate + Flux before(T value) { + return Mono.just(value).repeat().take(1); } @AfterTemplate - Flux after(int start) { - return Flux.just(start); + Flux after(T value) { + return Flux.just(value); } } @@ -566,6 +565,7 @@ final class ReactorRules { @Matches(IsIdentityOperation.class) Function> identityOperation) { return Refaster.anyOf( + flux.concatMap(function, 0), flux.flatMap(function, 1), flux.flatMapSequential(function, 1), flux.map(function).concatMap(identityOperation)); @@ -1206,10 +1206,17 @@ final class ReactorRules { } /** Prefer {@link Flux#fromIterable(Iterable)} over less efficient alternatives. */ + // XXX: Once the `FluxFromStreamSupplier` rule is constrained using + // `@NotMatches(IsIdentityOperation.class)`, this rule should also cover + // `Flux.fromStream(collection.stream())`. static final class FluxFromIterable { + // XXX: Once the `MethodReferenceUsage` check is generally enabled, drop the second + // `Refaster.anyOf` variant. @BeforeTemplate Flux before(Collection collection) { - return Flux.fromStream(collection.stream()); + return Flux.fromStream( + Refaster.>>anyOf( + collection::stream, () -> collection.stream())); } @AfterTemplate @@ -1761,6 +1768,60 @@ final class ReactorRules { } } + /** + * Prefer {@link StepVerifier#verify()} over a dangling {@link + * StepVerifier#verifyThenAssertThat()}. + */ + // XXX: Application of this rule (and several others in this class) will cause invalid code if the + // result of the rewritten expression is dereferenced. Consider introducing a bug checker that + // identifies rules that change the return type of an expression and annotates them accordingly. + // The associated annotation can then be used to instruct an annotation processor to generate + // corresponding `void` rules that match only statements. This would allow the `Refaster` check to + // conditionally skip "not fully safe" rules. This allows conditionally flagging more dubious + // code, at the risk of compilation failures. With this rule, for example, we want to explicitly + // nudge users towards `StepVerifier.Step#assertNext(Consumer)` or + // `StepVerifier.Step#expectNext(Object)`, together with `Step#verifyComplete()`. + static final class StepVerifierVerify { + @BeforeTemplate + StepVerifier.Assertions before(StepVerifier stepVerifier) { + return stepVerifier.verifyThenAssertThat(); + } + + @AfterTemplate + Duration after(StepVerifier stepVerifier) { + return stepVerifier.verify(); + } + } + + /** + * Prefer {@link StepVerifier#verify(Duration)} over a dangling {@link + * StepVerifier#verifyThenAssertThat(Duration)}. + */ + static final class StepVerifierVerifyDuration { + @BeforeTemplate + StepVerifier.Assertions before(StepVerifier stepVerifier, Duration duration) { + return stepVerifier.verifyThenAssertThat(duration); + } + + @AfterTemplate + Duration after(StepVerifier stepVerifier, Duration duration) { + return stepVerifier.verify(duration); + } + } + + /** Don't unnecessarily invoke {@link StepVerifier#verifyLater()} multiple times. */ + static final class StepVerifierVerifyLater { + @BeforeTemplate + StepVerifier before(StepVerifier stepVerifier) { + return stepVerifier.verifyLater().verifyLater(); + } + + @AfterTemplate + StepVerifier after(StepVerifier stepVerifier) { + return stepVerifier.verifyLater(); + } + } + /** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */ static final class StepVerifierStepIdentity { @BeforeTemplate @@ -1861,6 +1922,12 @@ final class ReactorRules { return step.expectErrorMatches(predicate).verify(); } + @BeforeTemplate + @SuppressWarnings("StepVerifierVerify" /* This is a more specific template. */) + StepVerifier.Assertions before2(StepVerifier.LastStep step, Predicate predicate) { + return step.expectError().verifyThenAssertThat().hasOperatorErrorMatching(predicate); + } + @AfterTemplate Duration after(StepVerifier.LastStep step, Predicate predicate) { return step.verifyErrorMatches(predicate); @@ -1883,6 +1950,30 @@ final class ReactorRules { } } + /** + * Prefer {@link StepVerifier.LastStep#verifyErrorSatisfies(Consumer)} with AssertJ over more + * contrived alternatives. + */ + static final class StepVerifierLastStepVerifyErrorSatisfiesAssertJ { + @BeforeTemplate + @SuppressWarnings("StepVerifierVerify" /* This is a more specific template. */) + StepVerifier.Assertions before(StepVerifier.LastStep step, Class clazz, String message) { + return Refaster.anyOf( + step.expectError() + .verifyThenAssertThat() + .hasOperatorErrorOfType(clazz) + .hasOperatorErrorWithMessage(message), + step.expectError(clazz).verifyThenAssertThat().hasOperatorErrorWithMessage(message), + step.expectErrorMessage(message).verifyThenAssertThat().hasOperatorErrorOfType(clazz)); + } + + @AfterTemplate + @UseImportPolicy(STATIC_IMPORT_ALWAYS) + Duration after(StepVerifier.LastStep step, Class clazz, String message) { + return step.verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(clazz).hasMessage(message)); + } + } + /** * Prefer {@link StepVerifier.LastStep#verifyErrorMessage(String)} over more verbose alternatives. */ @@ -1912,4 +2003,76 @@ final class ReactorRules { return step.verifyTimeout(duration); } } + + /** + * Prefer {@link Mono#fromFuture(Supplier)} over {@link Mono#fromFuture(CompletableFuture)}, as + * the former may defer initiation of the asynchronous computation until subscription. + */ + static final class MonoFromFutureSupplier { + // XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once + // `IsIdentityOperation` no longer matches nullary method invocations. + @BeforeTemplate + Mono before(CompletableFuture future) { + return Mono.fromFuture(future); + } + + @AfterTemplate + Mono after(CompletableFuture future) { + return Mono.fromFuture(() -> future); + } + } + + /** + * Prefer {@link Mono#fromFuture(Supplier, boolean)} over {@link + * Mono#fromFuture(CompletableFuture, boolean)}, as the former may defer initiation of the + * asynchronous computation until subscription. + */ + static final class MonoFromFutureSupplierBoolean { + // XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once + // `IsIdentityOperation` no longer matches nullary method invocations. + @BeforeTemplate + Mono before(CompletableFuture future, boolean suppressCancel) { + return Mono.fromFuture(future, suppressCancel); + } + + @AfterTemplate + Mono after(CompletableFuture future, boolean suppressCancel) { + return Mono.fromFuture(() -> future, suppressCancel); + } + } + + /** + * Don't propagate {@link Mono} cancellations to an upstream cache value computation, as + * completion of such computations may benefit concurrent or subsequent cache usages. + */ + static final class MonoFromFutureAsyncLoadingCacheGet { + @BeforeTemplate + Mono before(AsyncLoadingCache cache, K key) { + return Mono.fromFuture(() -> cache.get(key)); + } + + @AfterTemplate + Mono after(AsyncLoadingCache cache, K key) { + return Mono.fromFuture(() -> cache.get(key), /* suppressCancel= */ true); + } + } + + /** + * Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former + * yields a {@link Flux} that is more likely to behave as expected when subscribed to more than + * once. + */ + static final class FluxFromStreamSupplier { + // XXX: Constrain the `stream` parameter using `@NotMatches(IsIdentityOperation.class)` once + // `IsIdentityOperation` no longer matches nullary method invocations. + @BeforeTemplate + Flux before(Stream stream) { + return Flux.fromStream(stream); + } + + @AfterTemplate + Flux after(Stream stream) { + return Flux.fromStream(() -> stream); + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StreamRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StreamRules.java index 5ab7f9de..e7bea741 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StreamRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StreamRules.java @@ -25,6 +25,7 @@ import com.google.common.collect.Streams; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.refaster.Refaster; import com.google.errorprone.refaster.annotation.AfterTemplate; +import com.google.errorprone.refaster.annotation.AlsoNegation; import com.google.errorprone.refaster.annotation.BeforeTemplate; import com.google.errorprone.refaster.annotation.Matches; import com.google.errorprone.refaster.annotation.MayOptionallyUse; @@ -256,7 +257,7 @@ final class StreamRules { // XXX: This rule assumes that any matched `Collector` does not perform any filtering. // (Perhaps we could add a `@Matches` guard that validates that the collector expression does not // contain a `Collectors#filtering` call. That'd still not be 100% accurate, though.) - static final class StreamIsEmpty, M extends Map> { + static final class StreamFindAnyIsEmpty, M extends Map> { @BeforeTemplate boolean before(Stream stream, Collector collector) { return Refaster.anyOf( @@ -274,20 +275,20 @@ final class StreamRules { } @AfterTemplate + @AlsoNegation boolean after(Stream stream) { return stream.findAny().isEmpty(); } } - /** In order to test whether a stream has any element, simply try to find one. */ - static final class StreamIsNotEmpty { + /** + * Prefer {@link Stream#findAny()} over {@link Stream#findFirst()} if one only cares whether the + * stream is nonempty. + */ + static final class StreamFindAnyIsPresent { @BeforeTemplate boolean before(Stream stream) { - return Refaster.anyOf( - stream.count() != 0, - stream.count() > 0, - stream.count() >= 1, - stream.findFirst().isPresent()); + return stream.findFirst().isPresent(); } @AfterTemplate diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StringRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StringRules.java index f3610a86..2ae04b74 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StringRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/StringRules.java @@ -244,4 +244,105 @@ final class StringRules { return Utf8.encodedLength(str); } } + + /** Prefer {@link String#indexOf(int, int)} over less efficient alternatives. */ + static final class StringIndexOfChar { + @BeforeTemplate + @SuppressWarnings("java:S4635" /* This violation will be rewritten. */) + int before(String string, int ch, int fromIndex) { + return string.substring(fromIndex).indexOf(ch); + } + + @AfterTemplate + int after(String string, int ch, int fromIndex) { + return Math.max(-1, string.indexOf(ch, fromIndex) - fromIndex); + } + } + + /** Prefer {@link String#indexOf(String, int)} over less efficient alternatives. */ + static final class StringIndexOfString { + @BeforeTemplate + @SuppressWarnings("java:S4635" /* This violation will be rewritten. */) + int before(String string, String substring, int fromIndex) { + return string.substring(fromIndex).indexOf(substring); + } + + @AfterTemplate + int after(String string, String substring, int fromIndex) { + return Math.max(-1, string.indexOf(substring, fromIndex) - fromIndex); + } + } + + // XXX: Once we compile Refaster templates with JDK 21 also suggest `String#indexOf(int, int, + // int)` and `String#indexOf(String, int, int)`. + + /** Prefer {@link String#lastIndexOf(int, int)} over less efficient alternatives. */ + static final class StringLastIndexOfChar { + @BeforeTemplate + @SuppressWarnings("java:S4635" /* This violation will be rewritten. */) + int before(String string, int ch, int fromIndex) { + return string.substring(fromIndex).lastIndexOf(ch); + } + + @AfterTemplate + int after(String string, int ch, int fromIndex) { + return Math.max(-1, string.lastIndexOf(ch) - fromIndex); + } + } + + /** Prefer {@link String#lastIndexOf(String, int)} over less efficient alternatives. */ + static final class StringLastIndexOfString { + @BeforeTemplate + @SuppressWarnings("java:S4635" /* This violation will be rewritten. */) + int before(String string, String substring, int fromIndex) { + return string.substring(fromIndex).lastIndexOf(substring); + } + + @AfterTemplate + int after(String string, String substring, int fromIndex) { + return Math.max(-1, string.lastIndexOf(substring) - fromIndex); + } + } + + /** Prefer {@link String#lastIndexOf(int, int)} over less efficient alternatives. */ + static final class StringLastIndexOfCharWithIndex { + @BeforeTemplate + int before(String string, int ch, int fromIndex) { + return string.substring(0, fromIndex).lastIndexOf(ch); + } + + @AfterTemplate + int after(String string, int ch, int fromIndex) { + return string.lastIndexOf(ch, fromIndex - 1); + } + } + + /** Prefer {@link String#lastIndexOf(String, int)} over less efficient alternatives. */ + // XXX: The replacement expression isn't fully equivalent: in case `substring` is empty, then + // the replacement yields `fromIndex - 1` rather than `fromIndex`. + static final class StringLastIndexOfStringWithIndex { + @BeforeTemplate + int before(String string, String substring, int fromIndex) { + return string.substring(0, fromIndex).lastIndexOf(substring); + } + + @AfterTemplate + int after(String string, String substring, int fromIndex) { + return string.lastIndexOf(substring, fromIndex - 1); + } + } + + /** Prefer {@link String#startsWith(String, int)} over less efficient alternatives. */ + static final class StringStartsWith { + @BeforeTemplate + @SuppressWarnings("java:S4635" /* This violation will be rewritten. */) + boolean before(String string, String prefix, int fromIndex) { + return string.substring(fromIndex).startsWith(prefix); + } + + @AfterTemplate + boolean after(String string, String prefix, int fromIndex) { + return string.startsWith(prefix, fromIndex); + } + } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TestNGToAssertJRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TestNGToAssertJRules.java index e9662898..987812ac 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TestNGToAssertJRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TestNGToAssertJRules.java @@ -161,8 +161,9 @@ final class TestNGToAssertJRules { @AfterTemplate @DoNotCall + @UseImportPolicy(STATIC_IMPORT_ALWAYS) void after() { - throw new AssertionError(); + fail(); } } diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TimeRules.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TimeRules.java index 9e48cf80..be2382d5 100644 --- a/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TimeRules.java +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/TimeRules.java @@ -142,6 +142,63 @@ final class TimeRules { } } + /** Don't unnecessarily transform an {@link Instant} to an equivalent instance. */ + static final class InstantIdentity { + @BeforeTemplate + Instant before(Instant instant, TemporalUnit temporalUnit) { + return Refaster.anyOf( + instant.plus(Duration.ZERO), + instant.plus(0, temporalUnit), + instant.plusNanos(0), + instant.plusMillis(0), + instant.plusSeconds(0), + instant.minus(Duration.ZERO), + instant.minus(0, temporalUnit), + instant.minusNanos(0), + instant.minusMillis(0), + instant.minusSeconds(0), + Instant.parse(instant.toString()), + instant.truncatedTo(ChronoUnit.NANOS), + Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano())); + } + + @AfterTemplate + Instant after(Instant instant) { + return instant; + } + } + + /** + * Prefer {@link Instant#truncatedTo(TemporalUnit)} over less obvious alternatives. + * + *

Note that {@link Instant#toEpochMilli()} throws an {@link ArithmeticException} for dates + * very far in the past or future, while the suggested alternative doesn't. + */ + static final class InstantTruncatedToMilliseconds { + @BeforeTemplate + Instant before(Instant instant) { + return Instant.ofEpochMilli(instant.toEpochMilli()); + } + + @AfterTemplate + Instant after(Instant instant) { + return instant.truncatedTo(ChronoUnit.MILLIS); + } + } + + /** Prefer {@link Instant#truncatedTo(TemporalUnit)} over less obvious alternatives. */ + static final class InstantTruncatedToSeconds { + @BeforeTemplate + Instant before(Instant instant) { + return Instant.ofEpochSecond(instant.getEpochSecond()); + } + + @AfterTemplate + Instant after(Instant instant) { + return instant.truncatedTo(ChronoUnit.SECONDS); + } + } + /** Prefer {@link Instant#atOffset(ZoneOffset)} over more verbose alternatives. */ static final class InstantAtOffset { @BeforeTemplate diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsageTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsageTest.java new file mode 100644 index 00000000..ed5c8f3e --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ClassCastLambdaUsageTest.java @@ -0,0 +1,70 @@ +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 ClassCastLambdaUsageTest { + @Test + void identification() { + CompilationTestHelper.newInstance(ClassCastLambdaUsage.class, getClass()) + .addSourceLines( + "A.java", + "import com.google.common.collect.ImmutableSet;", + "import java.util.stream.IntStream;", + "import java.util.stream.Stream;", + "", + "class A {", + " void m() {", + " Number localVariable = 0;", + "", + " Stream.of(0).map(i -> i);", + " Stream.of(1).map(i -> i + 1);", + " Stream.of(2).map(Integer.class::cast);", + " Stream.of(3).map(i -> (Integer) 2);", + " Stream.of(4).map(i -> (Integer) localVariable);", + " // XXX: Ideally this case is also flagged. Pick this up in the context of merging the", + " // `ClassCastLambdaUsage` and `MethodReferenceUsage` checks, or introduce a separate check that", + " // simplifies unnecessary block lambda expressions.", + " Stream.of(5)", + " .map(", + " i -> {", + " return (Integer) i;", + " });", + " Stream.of(ImmutableSet.of(5)).map(s -> (ImmutableSet) s);", + " Stream.of(ImmutableSet.of(6)).map(s -> (ImmutableSet) s);", + " Stream.of(7).reduce((a, b) -> (Integer) a);", + " IntStream.of(8).mapToObj(i -> (char) i);", + "", + " // BUG: Diagnostic contains:", + " Stream.of(8).map(i -> (Integer) i);", + " }", + "}") + .doTest(); + } + + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(ClassCastLambdaUsage.class, getClass()) + .addInputLines( + "A.java", + "import java.util.stream.Stream;", + "", + "class A {", + " void m() {", + " Stream.of(1).map(i -> (Integer) i);", + " }", + "}") + .addOutputLines( + "A.java", + "import java.util.stream.Stream;", + "", + "class A {", + " void m() {", + " Stream.of(1).map(Integer.class::cast);", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ConstantNamingTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ConstantNamingTest.java new file mode 100644 index 00000000..889f8599 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ConstantNamingTest.java @@ -0,0 +1,78 @@ +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 ConstantNamingTest { + @Test + void identification() { + CompilationTestHelper.newInstance(ConstantNaming.class, getClass()) + .addSourceLines( + "A.java", + "class A {", + " private static final long serialVersionUID = 1L;", + " private static final int FOO = 1;", + " // BUG: Diagnostic contains: consider renaming to 'BAR', though note that this is not a private", + " // constant", + " static final int bar = 2;", + " // BUG: Diagnostic contains:", + " private static final int baz = 3;", + " // BUG: Diagnostic contains: consider renaming to 'QUX_QUUX', though note that a variable with", + " // this name is already declared", + " private static final int qux_QUUX = 4;", + " // BUG: Diagnostic contains: consider renaming to 'QUUZ', though note that a variable with", + " // this name is already declared", + " private static final int quuz = 3;", + "", + " private final int foo = 4;", + " private final Runnable QUX_QUUX =", + " new Runnable() {", + " private static final int QUUZ = 1;", + "", + " @Override", + " public void run() {}", + " };", + "}") + .doTest(); + } + + @Test + void identificationWithCustomExemption() { + CompilationTestHelper.newInstance(ConstantNaming.class, getClass()) + .setArgs("-XepOpt:CanonicalConstantNaming:ExemptedNames=foo,baz") + .addSourceLines( + "A.java", + "class A {", + " private static final long serialVersionUID = 1L;", + " private static final int foo = 1;", + " // BUG: Diagnostic contains:", + " private static final int bar = 2;", + " private static final int baz = 3;", + "}") + .doTest(); + } + + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(ConstantNaming.class, getClass()) + .addInputLines( + "A.java", + "class A {", + " static final int foo = 1;", + " private static final int bar = 2;", + " private static final int baz = 3;", + " private static final int BAZ = 4;", + "}") + .addOutputLines( + "A.java", + "class A {", + " static final int foo = 1;", + " private static final int BAR = 2;", + " private static final int baz = 3;", + " private static final int BAZ = 4;", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IdentityConversionTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IdentityConversionTest.java index 631acdad..f9286f4e 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IdentityConversionTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IdentityConversionTest.java @@ -28,6 +28,8 @@ final class IdentityConversionTest { "import com.google.common.collect.ImmutableTable;", "import com.google.errorprone.matchers.Matcher;", "import com.google.errorprone.matchers.Matchers;", + "import java.time.Instant;", + "import java.time.ZonedDateTime;", "import reactor.adapter.rxjava.RxJava2Adapter;", "import reactor.core.publisher.Flux;", "import reactor.core.publisher.Mono;", @@ -149,6 +151,10 @@ final class IdentityConversionTest { " // BUG: Diagnostic contains:", " ImmutableTable o11 = ImmutableTable.copyOf(ImmutableTable.of());", "", + " Instant instant1 = Instant.from(ZonedDateTime.now());", + " // BUG: Diagnostic contains:", + " Instant instant2 = Instant.from(Instant.now());", + "", " // BUG: Diagnostic contains:", " Matcher allOf1 = Matchers.allOf(instanceMethod());", " Matcher allOf2 = Matchers.allOf(instanceMethod(), staticMethod());", diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsageTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsageTest.java index a6204c5a..e57f0fbd 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsageTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/IsInstanceLambdaUsageTest.java @@ -18,22 +18,23 @@ final class IsInstanceLambdaUsageTest { " void m() {", " Integer localVariable = 0;", "", - " Stream.of(0).map(i -> i + 1);", - " Stream.of(1).filter(Integer.class::isInstance);", - " Stream.of(2).filter(i -> i.getClass() instanceof Class);", - " Stream.of(3).filter(i -> localVariable instanceof Integer);", + " Stream.of(0).map(i -> i);", + " Stream.of(1).map(i -> i + 1);", + " Stream.of(2).filter(Integer.class::isInstance);", + " Stream.of(3).filter(i -> i.getClass() instanceof Class);", + " Stream.of(4).filter(i -> localVariable instanceof Integer);", " // XXX: Ideally this case is also flagged. Pick this up in the context of merging the", " // `IsInstanceLambdaUsage` and `MethodReferenceUsage` checks, or introduce a separate check that", " // simplifies unnecessary block lambda expressions.", - " Stream.of(4)", + " Stream.of(5)", " .filter(", " i -> {", - " return localVariable instanceof Integer;", + " return i instanceof Integer;", " });", - " Flux.just(5, \"foo\").distinctUntilChanged(v -> v, (a, b) -> a instanceof Integer);", + " Flux.just(6, \"foo\").distinctUntilChanged(v -> v, (a, b) -> a instanceof Integer);", "", " // BUG: Diagnostic contains:", - " Stream.of(6).filter(i -> i instanceof Integer);", + " Stream.of(7).filter(i -> i instanceof Integer);", " }", "}") .doTest(); diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListingTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListingTest.java index da842fd9..b73458a5 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListingTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/LexicographicalAnnotationAttributeListingTest.java @@ -29,6 +29,10 @@ final class LexicographicalAnnotationAttributeListingTest { " @interface Foo {", " String[] value() default {};", "", + " boolean[] bools() default {};", + "", + " char[] chars() default {};", + "", " int[] ints() default {};", "", " Class[] cls() default {};", @@ -69,6 +73,32 @@ final class LexicographicalAnnotationAttributeListingTest { " @Foo({\"a\", \"A\"})", " A unsortedStringCaseInsensitiveWithTotalOrderFallback();", "", + " @Foo(bools = {})", + " A noBools();", + "", + " @Foo(bools = {false})", + " A oneBool();", + "", + " @Foo(bools = {false, true})", + " A sortedBools();", + "", + " // BUG: Diagnostic contains:", + " @Foo(bools = {true, false})", + " A unsortedBools();", + "", + " @Foo(chars = {})", + " A noChars();", + "", + " @Foo(chars = {'a'})", + " A oneChar();", + "", + " @Foo(chars = {'a', 'b'})", + " A sortedChars();", + "", + " // BUG: Diagnostic contains:", + " @Foo(chars = {'b', 'a'})", + " A unsortedChars();", + "", " @Foo(ints = {})", " A noInts();", "", @@ -173,6 +203,10 @@ final class LexicographicalAnnotationAttributeListingTest { " @interface Foo {", " String[] value() default {};", "", + " boolean[] bools() default {};", + "", + " char[] chars() default {};", + "", " Class[] cls() default {};", "", " RoundingMode[] enums() default {};", @@ -185,7 +219,13 @@ final class LexicographicalAnnotationAttributeListingTest { " }", "", " @Foo({\" \", \"\", \"b\", \"a\"})", - " A unsortedString();", + " A unsortedStrings();", + "", + " @Foo(bools = {true, false})", + " A unsortedBooleans();", + "", + " @Foo(chars = {'b', 'a'})", + " A unsortedChars();", "", " @Foo(cls = {long.class, int.class})", " A unsortedClasses();", @@ -210,6 +250,10 @@ final class LexicographicalAnnotationAttributeListingTest { " @interface Foo {", " String[] value() default {};", "", + " boolean[] bools() default {};", + "", + " char[] chars() default {};", + "", " Class[] cls() default {};", "", " RoundingMode[] enums() default {};", @@ -222,7 +266,13 @@ final class LexicographicalAnnotationAttributeListingTest { " }", "", " @Foo({\"\", \" \", \"a\", \"b\"})", - " A unsortedString();", + " A unsortedStrings();", + "", + " @Foo(bools = {false, true})", + " A unsortedBooleans();", + "", + " @Foo(chars = {'a', 'b'})", + " A unsortedChars();", "", " @Foo(cls = {int.class, long.class})", " A unsortedClasses();", diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGetTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGetTest.java new file mode 100644 index 00000000..86372b15 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/OptionalOrElseGetTest.java @@ -0,0 +1,137 @@ +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 OptionalOrElseGetTest { + @Test + void identification() { + CompilationTestHelper.newInstance(OptionalOrElseGet.class, getClass()) + .addSourceLines( + "A.java", + "import com.google.errorprone.refaster.Refaster;", + "import java.util.Optional;", + "import java.util.function.Supplier;", + "", + "class A {", + " private final Optional optional = Optional.empty();", + " private final String string = optional.toString();", + "", + " void m() {", + " Optional.empty().orElse(null);", + " optional.orElse(null);", + " optional.orElse(\"constant\");", + " optional.orElse(\"constant\" + 0);", + " optional.orElse(Boolean.TRUE);", + " optional.orElse(string);", + " optional.orElse(this.string);", + " optional.orElse(Refaster.anyOf(\"constant\", \"another\"));", + " Optional.>empty().orElse(() -> \"constant\");", + "", + " // BUG: Diagnostic contains:", + " Optional.empty().orElse(string + \"constant\");", + " // BUG: Diagnostic contains:", + " optional.orElse(string + \"constant\");", + " // BUG: Diagnostic contains:", + " optional.orElse(\"constant\".toString());", + " // BUG: Diagnostic contains:", + " optional.orElse(string.toString());", + " // BUG: Diagnostic contains:", + " optional.orElse(this.string.toString());", + " // BUG: Diagnostic contains:", + " optional.orElse(String.valueOf(42));", + " // BUG: Diagnostic contains:", + " optional.orElse(string.toString().length());", + " // BUG: Diagnostic contains:", + " optional.orElse(\"constant\".equals(string));", + " // BUG: Diagnostic contains:", + " optional.orElse(string.equals(string));", + " // BUG: Diagnostic contains:", + " optional.orElse(this.string.equals(string));", + " // BUG: Diagnostic contains:", + " optional.orElse(foo());", + " // BUG: Diagnostic contains:", + " optional.orElse(this.foo());", + " // BUG: Diagnostic contains:", + " optional.orElse(new Object() {});", + " // BUG: Diagnostic contains:", + " optional.orElse(new int[0].length);", + " }", + "", + " private T foo() {", + " return null;", + " }", + "}") + .doTest(); + } + + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(OptionalOrElseGet.class, getClass()) + .addInputLines( + "A.java", + "import java.util.Optional;", + "", + "class A {", + " private final Optional optional = Optional.empty();", + " private final String string = optional.toString();", + "", + " void m() {", + " optional.orElse(string + \"constant\");", + " optional.orElse(\"constant\".toString());", + " optional.orElse(string.toString());", + " optional.orElse(this.string.toString());", + " optional.orElse(String.valueOf(42));", + " optional.orElse(string.toString().length());", + " optional.orElse(string.equals(string));", + " optional.orElse(foo());", + " optional.orElse(this.foo());", + " optional.orElse(this.bar());", + " optional.orElse(new Object() {});", + " optional.orElse(new int[0].length);", + " }", + "", + " private T foo() {", + " return null;", + " }", + "", + " private T bar() {", + " return null;", + " }", + "}") + .addOutputLines( + "A.java", + "import java.util.Optional;", + "", + "class A {", + " private final Optional optional = Optional.empty();", + " private final String string = optional.toString();", + "", + " void m() {", + " optional.orElseGet(() -> string + \"constant\");", + " optional.orElseGet(\"constant\"::toString);", + " optional.orElseGet(string::toString);", + " optional.orElseGet(this.string::toString);", + " optional.orElseGet(() -> String.valueOf(42));", + " optional.orElseGet(() -> string.toString().length());", + " optional.orElseGet(() -> string.equals(string));", + " optional.orElseGet(() -> foo());", + " optional.orElseGet(this::foo);", + " optional.orElseGet(this::bar);", + " optional.orElseGet(() -> new Object() {});", + " optional.orElseGet(() -> new int[0].length);", + " }", + "", + " private T foo() {", + " return null;", + " }", + "", + " private T bar() {", + " return null;", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclarationTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclarationTest.java new file mode 100644 index 00000000..ada419cc --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/Slf4jLoggerDeclarationTest.java @@ -0,0 +1,219 @@ +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 Slf4jLoggerDeclarationTest { + @Test + void identification() { + CompilationTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass()) + .addSourceLines( + "A.java", + "import static java.lang.Class.forName;", + "", + "import org.slf4j.Logger;", + "import org.slf4j.LoggerFactory;", + "", + "class A {", + " private static final long serialVersionUID = 1L;", + " private static final Logger LOG = LoggerFactory.getLogger(A.class);", + "", + " abstract static class DynamicLogger {", + " private final Logger log = LoggerFactory.getLogger(getClass());", + " }", + "", + " abstract static class DynamicLoggerWithExplicitThis {", + " private final Logger log = LoggerFactory.getLogger(this.getClass());", + " }", + "", + " static final class StaticLogger {", + " private static final Logger LOG = LoggerFactory.getLogger(StaticLogger.class);", + " }", + "", + " static final class StaticLoggerWithCustomIdentifier {", + " private static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " interface StaticLoggerForInterface {", + " Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterface.class);", + " }", + "", + " abstract static class DynamicLoggerForWrongTypeWithoutReceiver {", + " // BUG: Diagnostic contains:", + " private final Logger log = LoggerFactory.getLogger(forName(\"A.class\"));", + "", + " DynamicLoggerForWrongTypeWithoutReceiver() throws ClassNotFoundException {}", + " }", + "", + " abstract static class DynamicLoggerForWrongTypeWithoutSymbol {", + " // BUG: Diagnostic contains:", + " private final Logger log = LoggerFactory.getLogger(\"foo\".getClass());", + " }", + "", + " abstract static class DynamicLoggerForWrongTypeWithSymbol {", + " // BUG: Diagnostic contains:", + " private final Logger log = LoggerFactory.getLogger(new A().getClass());", + " }", + "", + " static final class NonAbstractDynamicLogger {", + " // BUG: Diagnostic contains:", + " private final Logger log = LoggerFactory.getLogger(getClass());", + " }", + "", + " abstract static class DynamicLoggerWithMissingModifier {", + " // BUG: Diagnostic contains:", + " final Logger log = LoggerFactory.getLogger(getClass());", + " }", + "", + " abstract static class DynamicLoggerWithExcessModifier {", + " // BUG: Diagnostic contains:", + " private final transient Logger log = LoggerFactory.getLogger(getClass());", + " }", + "", + " abstract static class MisnamedDynamicLogger {", + " // BUG: Diagnostic contains:", + " private final Logger LOG = LoggerFactory.getLogger(getClass());", + " }", + "", + " static final class StaticLoggerWithMissingModifier {", + " // BUG: Diagnostic contains:", + " static final Logger LOG = LoggerFactory.getLogger(StaticLoggerWithMissingModifier.class);", + " }", + "", + " static final class StaticLoggerWithExcessModifier {", + " // BUG: Diagnostic contains:", + " private static final transient Logger LOG =", + " LoggerFactory.getLogger(StaticLoggerWithExcessModifier.class);", + " }", + "", + " static final class MisnamedStaticLogger {", + " // BUG: Diagnostic contains:", + " private static final Logger log = LoggerFactory.getLogger(MisnamedStaticLogger.class);", + " }", + "", + " static final class StaticLoggerWithIncorrectIdentifier {", + " // BUG: Diagnostic contains:", + " private static final Logger LOG = LoggerFactory.getLogger(A.class);", + " }", + "", + " static final class StaticLoggerWithCustomIdentifierAndMissingModifier {", + " // BUG: Diagnostic contains:", + " static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " static final class StaticLoggerWithCustomIdentifierAndExcessModifier {", + " // BUG: Diagnostic contains:", + " private static final transient Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " static final class MisnamedStaticLoggerWithCustomIdentifier {", + " // BUG: Diagnostic contains:", + " private static final Logger log = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " interface StaticLoggerForInterfaceWithExcessModifier {", + " // BUG: Diagnostic contains:", + " static Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterfaceWithExcessModifier.class);", + " }", + "", + " interface MisnamedStaticLoggerForInterface {", + " // BUG: Diagnostic contains:", + " Logger log = LoggerFactory.getLogger(MisnamedStaticLoggerForInterface.class);", + " }", + "", + " interface StaticLoggerForInterfaceWithIncorrectIdentifier {", + " // BUG: Diagnostic contains:", + " Logger LOG = LoggerFactory.getLogger(A.class);", + " }", + "}") + .doTest(); + } + + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass()) + .addInputLines( + "A.java", + "import org.slf4j.Logger;", + "import org.slf4j.LoggerFactory;", + "", + "class A {", + " static Logger foo = LoggerFactory.getLogger(Logger.class);", + "", + " abstract static class DynamicLogger {", + " transient Logger BAR = LoggerFactory.getLogger(getClass());", + " }", + "", + " static final class StaticLogger {", + " transient Logger baz = LoggerFactory.getLogger(LoggerFactory.class);", + " }", + "", + " static final class StaticLoggerWithCustomIdentifier {", + " transient Logger qux = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " interface StaticLoggerForInterface {", + " public static final Logger quux = LoggerFactory.getLogger(A.class);", + " }", + "}") + .addOutputLines( + "A.java", + "import org.slf4j.Logger;", + "import org.slf4j.LoggerFactory;", + "", + "class A {", + " private static final Logger LOG = LoggerFactory.getLogger(A.class);", + "", + " abstract static class DynamicLogger {", + " private final Logger log = LoggerFactory.getLogger(getClass());", + " }", + "", + " static final class StaticLogger {", + " private static final Logger LOG = LoggerFactory.getLogger(StaticLogger.class);", + " }", + "", + " static final class StaticLoggerWithCustomIdentifier {", + " private static final Logger LOG = LoggerFactory.getLogger(\"custom-identifier\");", + " }", + "", + " interface StaticLoggerForInterface {", + " Logger LOG = LoggerFactory.getLogger(StaticLoggerForInterface.class);", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } + + @Test + void replacementWithCustomLoggerName() { + BugCheckerRefactoringTestHelper.newInstance(Slf4jLoggerDeclaration.class, getClass()) + .setArgs(ImmutableList.of("-XepOpt:Slf4jLogDeclaration:CanonicalStaticLoggerName=FOO_BAR")) + .addInputLines( + "A.java", + "import org.slf4j.Logger;", + "import org.slf4j.LoggerFactory;", + "", + "class A {", + " transient Logger LOG = LoggerFactory.getLogger(Logger.class);", + "", + " abstract static class DynamicLogger {", + " transient Logger log = LoggerFactory.getLogger(getClass());", + " }", + "}") + .addOutputLines( + "A.java", + "import org.slf4j.Logger;", + "import org.slf4j.LoggerFactory;", + "", + "class A {", + " private static final Logger FOO_BAR = LoggerFactory.getLogger(A.class);", + "", + " abstract static class DynamicLogger {", + " private final Logger fooBar = LoggerFactory.getLogger(getClass());", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/refasterrules/RefasterRulesTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/refasterrules/RefasterRulesTest.java index a7c35621..fd5cb296 100644 --- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/refasterrules/RefasterRulesTest.java +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/refasterrules/RefasterRulesTest.java @@ -43,6 +43,7 @@ final class RefasterRulesTest { EqualityRules.class, FileRules.class, InputStreamRules.class, + ImmutableEnumSetRules.class, ImmutableListRules.class, ImmutableListMultimapRules.class, ImmutableMapRules.class, @@ -58,6 +59,7 @@ final class RefasterRulesTest { LongStreamRules.class, MapEntryRules.class, MapRules.class, + MicrometerRules.class, MockitoRules.class, MultimapRules.class, NullRules.class, diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestInput.java index 1f785293..98174797 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestInput.java @@ -1,10 +1,7 @@ package tech.picnic.errorprone.refasterrules; -import static com.google.common.collect.ImmutableSet.toImmutableSet; - import com.google.common.base.Preconditions; import com.google.common.base.Splitter; -import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -24,8 +21,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase { Preconditions.class, Sets.class, Splitter.class, - Streams.class, - toImmutableSet()); + Streams.class); } int testCheckIndex() { @@ -38,10 +34,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase { } } - ImmutableSet testStreamToImmutableEnumSet() { - return Stream.of(BoundType.OPEN).collect(toImmutableSet()); - } - ImmutableSet testIteratorGetNextOrDefault() { return ImmutableSet.of( ImmutableList.of("a").iterator().hasNext() diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestOutput.java index 2fd9c408..2e6794db 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/AssortedRulesTestOutput.java @@ -1,12 +1,9 @@ package tech.picnic.errorprone.refasterrules; -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Sets.toImmutableEnumSet; import static java.util.Objects.checkIndex; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; -import com.google.common.collect.BoundType; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -27,8 +24,7 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase { Preconditions.class, Sets.class, Splitter.class, - Streams.class, - toImmutableSet()); + Streams.class); } int testCheckIndex() { @@ -39,10 +35,6 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase { checkIndex(1, 2); } - ImmutableSet testStreamToImmutableEnumSet() { - return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet()); - } - ImmutableSet testIteratorGetNextOrDefault() { return ImmutableSet.of( Iterators.getNext(ImmutableList.of("a").iterator(), "foo"), diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestInput.java index 91b0bf59..6d1ac1db 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestInput.java @@ -5,6 +5,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper; import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers; import com.google.errorprone.bugpatterns.BugChecker; import com.sun.tools.javac.util.Convert; +import javax.lang.model.element.Name; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase { @@ -31,4 +32,10 @@ final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase { String testConstantsFormat() { return String.format("\"%s\"", Convert.quote("foo")); } + + ImmutableSet testNameContentEquals() { + return ImmutableSet.of( + ((Name) null).toString().equals("foo".subSequence(0, 1).toString()), + ((com.sun.tools.javac.util.Name) null).toString().equals("bar")); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestOutput.java index 013617aa..3227c420 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/BugCheckerRulesTestOutput.java @@ -6,6 +6,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers; import com.google.errorprone.bugpatterns.BugChecker; import com.sun.tools.javac.util.Constants; import com.sun.tools.javac.util.Convert; +import javax.lang.model.element.Name; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase { @@ -30,4 +31,10 @@ final class BugCheckerRulesTest implements RefasterRuleCollectionTestCase { String testConstantsFormat() { return Constants.format("foo"); } + + ImmutableSet testNameContentEquals() { + return ImmutableSet.of( + ((Name) null).contentEquals("foo".subSequence(0, 1)), + ((com.sun.tools.javac.util.Name) null).contentEquals("bar")); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestInput.java index 2e94c242..59897bc3 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestInput.java @@ -1,26 +1,30 @@ package tech.picnic.errorprone.refasterrules; import com.google.common.collect.ImmutableSet; -import java.io.IOException; +import java.util.function.Function; import java.util.function.Predicate; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class ClassRulesTest implements RefasterRuleCollectionTestCase { - boolean testClassIsInstance() throws IOException { + boolean testClassIsInstance() { return CharSequence.class.isAssignableFrom("foo".getClass()); } - ImmutableSet testInstanceof() throws IOException { + ImmutableSet testInstanceof() { Class clazz = CharSequence.class; return ImmutableSet.of(CharSequence.class.isInstance("foo"), clazz.isInstance("bar")); } - Predicate testClassLiteralIsInstancePredicate() throws IOException { + Predicate testClassLiteralIsInstancePredicate() { return s -> s instanceof CharSequence; } - Predicate testClassReferenceIsInstancePredicate() throws IOException { + Predicate testClassReferenceIsInstancePredicate() { Class clazz = CharSequence.class; return s -> clazz.isInstance(s); } + + Function testClassReferenceCast() { + return i -> Integer.class.cast(i); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestOutput.java index 39c08a50..fd205051 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ClassRulesTestOutput.java @@ -1,26 +1,30 @@ package tech.picnic.errorprone.refasterrules; import com.google.common.collect.ImmutableSet; -import java.io.IOException; +import java.util.function.Function; import java.util.function.Predicate; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class ClassRulesTest implements RefasterRuleCollectionTestCase { - boolean testClassIsInstance() throws IOException { + boolean testClassIsInstance() { return CharSequence.class.isInstance("foo"); } - ImmutableSet testInstanceof() throws IOException { + ImmutableSet testInstanceof() { Class clazz = CharSequence.class; return ImmutableSet.of("foo" instanceof CharSequence, clazz.isInstance("bar")); } - Predicate testClassLiteralIsInstancePredicate() throws IOException { + Predicate testClassLiteralIsInstancePredicate() { return CharSequence.class::isInstance; } - Predicate testClassReferenceIsInstancePredicate() throws IOException { + Predicate testClassReferenceIsInstancePredicate() { Class clazz = CharSequence.class; return clazz::isInstance; } + + Function testClassReferenceCast() { + return Integer.class::cast; + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestInput.java index 186da035..6936af32 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestInput.java @@ -6,9 +6,11 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Optional; import java.util.TreeSet; import java.util.stream.Stream; @@ -70,6 +72,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { } } + Stream testSetStream() { + return ImmutableSet.of(1).stream().distinct(); + } + ArrayList testNewArrayListFromCollection() { return Lists.newArrayList(ImmutableList.of("foo")); } @@ -94,6 +100,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(1).asList().toString(); } + List testArraysAsList() { + return Arrays.stream(new String[0]).toList(); + } + ImmutableSet testCollectionToArray() { return ImmutableSet.of( ImmutableSet.of(1).toArray(new Object[1]), @@ -109,8 +119,9 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(1).asList().toArray(Integer[]::new); } - Iterator testImmutableCollectionIterator() { - return ImmutableSet.of(1).asList().iterator(); + ImmutableSet> testCollectionIterator() { + return ImmutableSet.of( + ImmutableSet.of(1).stream().iterator(), ImmutableSet.of(2).asList().iterator()); } ImmutableSet> testOptionalFirstCollectionElement() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestOutput.java index ba09e13e..59367ba2 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/CollectionRulesTestOutput.java @@ -6,9 +6,11 @@ import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Optional; import java.util.TreeSet; import java.util.stream.Stream; @@ -62,6 +64,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { new HashSet().removeAll(ImmutableSet.of(2)); } + Stream testSetStream() { + return ImmutableSet.of(1).stream(); + } + ArrayList testNewArrayListFromCollection() { return new ArrayList<>(ImmutableList.of("foo")); } @@ -86,6 +92,10 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(1).toString(); } + List testArraysAsList() { + return Arrays.asList(new String[0]); + } + ImmutableSet testCollectionToArray() { return ImmutableSet.of( ImmutableSet.of(1).toArray(), ImmutableSet.of(2).toArray(), ImmutableSet.of(3).toArray()); @@ -99,8 +109,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(1).toArray(Integer[]::new); } - Iterator testImmutableCollectionIterator() { - return ImmutableSet.of(1).iterator(); + ImmutableSet> testCollectionIterator() { + return ImmutableSet.of(ImmutableSet.of(1).iterator(), ImmutableSet.of(2).iterator()); } ImmutableSet> testOptionalFirstCollectionElement() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestInput.java index 2d92ba5d..26330d28 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestInput.java @@ -9,6 +9,7 @@ import static java.util.stream.Collectors.minBy; import com.google.common.collect.Comparators; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -54,6 +55,10 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Comparator.comparing(s -> "foo", Comparator.comparingInt(String::length))); } + Comparator testComparingEnum() { + return Comparator.comparingInt(s -> RoundingMode.valueOf(s).ordinal()); + } + Comparator testThenComparing() { return Comparator.naturalOrder().thenComparing(Comparator.comparing(String::isEmpty)); } @@ -102,6 +107,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Comparator.reverseOrder().compare("baz", "qux")); } + void testCollectionsSort() { + Collections.sort(ImmutableList.of("foo", "bar"), naturalOrder()); + } + + ImmutableSet testCollectionsMin() { + return ImmutableSet.of( + Collections.min(ImmutableList.of("foo"), naturalOrder()), + Collections.max(ImmutableList.of("bar"), reverseOrder())); + } + + String testMinOfArray() { + return Arrays.stream(new String[0]).min(naturalOrder()).orElseThrow(); + } + + String testCollectionsMinWithComparator() { + return ImmutableSet.of("foo", "bar").stream().min(naturalOrder()).orElseThrow(); + } + int testMinOfVarargs() { return Stream.of(1, 2).min(naturalOrder()).orElseThrow(); } @@ -130,6 +153,20 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1)); } + ImmutableSet testCollectionsMax() { + return ImmutableSet.of( + Collections.max(ImmutableList.of("foo"), naturalOrder()), + Collections.min(ImmutableList.of("bar"), reverseOrder())); + } + + String testMaxOfArray() { + return Arrays.stream(new String[0]).max(naturalOrder()).orElseThrow(); + } + + String testCollectionsMaxWithComparator() { + return ImmutableSet.of("foo", "bar").stream().max(naturalOrder()).orElseThrow(); + } + int testMaxOfVarargs() { return Stream.of(1, 2).max(naturalOrder()).orElseThrow(); } @@ -173,4 +210,16 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Collector> testMaxByNaturalOrder() { return minBy(reverseOrder()); } + + ImmutableSet testIsLessThan() { + return ImmutableSet.of( + RoundingMode.UP.ordinal() < RoundingMode.DOWN.ordinal(), + RoundingMode.UP.ordinal() >= RoundingMode.DOWN.ordinal()); + } + + ImmutableSet testIsLessThanOrEqualTo() { + return ImmutableSet.of( + RoundingMode.UP.ordinal() <= RoundingMode.DOWN.ordinal(), + RoundingMode.UP.ordinal() > RoundingMode.DOWN.ordinal()); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestOutput.java index ff205855..885e13fc 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ComparatorRulesTestOutput.java @@ -1,5 +1,6 @@ package tech.picnic.errorprone.refasterrules; +import static java.util.Comparator.comparing; import static java.util.Comparator.naturalOrder; import static java.util.Comparator.reverseOrder; import static java.util.function.Function.identity; @@ -9,6 +10,7 @@ import static java.util.stream.Collectors.minBy; import com.google.common.collect.Comparators; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; @@ -52,6 +54,10 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Comparator.comparing(s -> "foo", Comparator.comparingInt(String::length))); } + Comparator testComparingEnum() { + return comparing(s -> RoundingMode.valueOf(s)); + } + Comparator testThenComparing() { return Comparator.naturalOrder().thenComparing(String::isEmpty); } @@ -92,6 +98,23 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz")); } + void testCollectionsSort() { + Collections.sort(ImmutableList.of("foo", "bar")); + } + + ImmutableSet testCollectionsMin() { + return ImmutableSet.of( + Collections.min(ImmutableList.of("foo")), Collections.min(ImmutableList.of("bar"))); + } + + String testMinOfArray() { + return Collections.min(Arrays.asList(new String[0]), naturalOrder()); + } + + String testCollectionsMinWithComparator() { + return Collections.min(ImmutableSet.of("foo", "bar"), naturalOrder()); + } + int testMinOfVarargs() { return Collections.min(Arrays.asList(1, 2), naturalOrder()); } @@ -120,6 +143,19 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Comparators.min("a", "b", (a, b) -> 1)); } + ImmutableSet testCollectionsMax() { + return ImmutableSet.of( + Collections.max(ImmutableList.of("foo")), Collections.max(ImmutableList.of("bar"))); + } + + String testMaxOfArray() { + return Collections.max(Arrays.asList(new String[0]), naturalOrder()); + } + + String testCollectionsMaxWithComparator() { + return Collections.max(ImmutableSet.of("foo", "bar"), naturalOrder()); + } + int testMaxOfVarargs() { return Collections.max(Arrays.asList(1, 2), naturalOrder()); } @@ -163,4 +199,16 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase { Collector> testMaxByNaturalOrder() { return maxBy(naturalOrder()); } + + ImmutableSet testIsLessThan() { + return ImmutableSet.of( + RoundingMode.UP.compareTo(RoundingMode.DOWN) < 0, + RoundingMode.UP.compareTo(RoundingMode.DOWN) >= 0); + } + + ImmutableSet testIsLessThanOrEqualTo() { + return ImmutableSet.of( + RoundingMode.UP.compareTo(RoundingMode.DOWN) <= 0, + RoundingMode.UP.compareTo(RoundingMode.DOWN) > 0); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestInput.java index 016580a6..986dabad 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestInput.java @@ -1,5 +1,6 @@ package tech.picnic.errorprone.refasterrules; +import static java.util.function.Predicate.isEqual; import static java.util.function.Predicate.not; import com.google.common.collect.BoundType; @@ -14,15 +15,21 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class EqualityRulesTest implements RefasterRuleCollectionTestCase { @Override public ImmutableSet elidedTypesAndStaticImports() { - return ImmutableSet.of(Objects.class, Optional.class, not(null)); + return ImmutableSet.of(Objects.class, Optional.class, isEqual(null), not(null)); } - ImmutableSet testPrimitiveOrReferenceEquality() { + ImmutableSet testEnumReferenceEquality() { return ImmutableSet.of( RoundingMode.UP.equals(RoundingMode.DOWN), Objects.equals(RoundingMode.UP, RoundingMode.DOWN), + RoundingMode.UP.ordinal() == RoundingMode.DOWN.ordinal(), !RoundingMode.UP.equals(RoundingMode.DOWN), - !Objects.equals(RoundingMode.UP, RoundingMode.DOWN)); + !Objects.equals(RoundingMode.UP, RoundingMode.DOWN), + RoundingMode.UP.ordinal() != RoundingMode.DOWN.ordinal()); + } + + ImmutableSet> testEnumReferenceEqualityLambda() { + return ImmutableSet.of(isEqual(RoundingMode.DOWN), RoundingMode.UP::equals); } boolean testEqualsPredicate() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestOutput.java index 39bab876..b89decfb 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/EqualityRulesTestOutput.java @@ -1,5 +1,6 @@ package tech.picnic.errorprone.refasterrules; +import static java.util.function.Predicate.isEqual; import static java.util.function.Predicate.not; import com.google.common.collect.BoundType; @@ -14,17 +15,23 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class EqualityRulesTest implements RefasterRuleCollectionTestCase { @Override public ImmutableSet elidedTypesAndStaticImports() { - return ImmutableSet.of(Objects.class, Optional.class, not(null)); + return ImmutableSet.of(Objects.class, Optional.class, isEqual(null), not(null)); } - ImmutableSet testPrimitiveOrReferenceEquality() { + ImmutableSet testEnumReferenceEquality() { return ImmutableSet.of( RoundingMode.UP == RoundingMode.DOWN, RoundingMode.UP == RoundingMode.DOWN, + RoundingMode.UP == RoundingMode.DOWN, + RoundingMode.UP != RoundingMode.DOWN, RoundingMode.UP != RoundingMode.DOWN, RoundingMode.UP != RoundingMode.DOWN); } + ImmutableSet> testEnumReferenceEqualityLambda() { + return ImmutableSet.of(v -> v == RoundingMode.DOWN, v -> v == RoundingMode.UP); + } + boolean testEqualsPredicate() { // XXX: When boxing is involved this rule seems to break. Example: // Stream.of(1).anyMatch(e -> Integer.MIN_VALUE.equals(e)); diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestInput.java index c33f3e67..4863bb1a 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestInput.java @@ -1,12 +1,28 @@ package tech.picnic.errorprone.refasterrules; +import com.google.common.collect.ImmutableSet; +import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class FileRulesTest implements RefasterRuleCollectionTestCase { + Path testPathOfUri() { + return Paths.get(URI.create("foo")); + } + + ImmutableSet testPathOfString() { + return ImmutableSet.of(Paths.get("foo"), Paths.get("bar", "baz", "qux")); + } + + Path testPathInstance() { + return Path.of("foo").toFile().toPath(); + } + String testFilesReadStringWithCharset() throws IOException { return new String(Files.readAllBytes(Paths.get("foo")), StandardCharsets.ISO_8859_1); } @@ -14,4 +30,13 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase { String testFilesReadString() throws IOException { return Files.readString(Paths.get("foo"), StandardCharsets.UTF_8); } + + ImmutableSet testFilesCreateTempFileToFile() throws IOException { + return ImmutableSet.of( + File.createTempFile("foo", "bar"), File.createTempFile("baz", "qux", null)); + } + + File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException { + return File.createTempFile("foo", "bar", new File("baz")); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestOutput.java index 5b516e31..0e987bcf 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/FileRulesTestOutput.java @@ -1,12 +1,28 @@ package tech.picnic.errorprone.refasterrules; +import com.google.common.collect.ImmutableSet; +import java.io.File; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class FileRulesTest implements RefasterRuleCollectionTestCase { + Path testPathOfUri() { + return Path.of(URI.create("foo")); + } + + ImmutableSet testPathOfString() { + return ImmutableSet.of(Path.of("foo"), Path.of("bar", "baz", "qux")); + } + + Path testPathInstance() { + return Path.of("foo"); + } + String testFilesReadStringWithCharset() throws IOException { return Files.readString(Paths.get("foo"), StandardCharsets.ISO_8859_1); } @@ -14,4 +30,13 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase { String testFilesReadString() throws IOException { return Files.readString(Paths.get("foo")); } + + ImmutableSet testFilesCreateTempFileToFile() throws IOException { + return ImmutableSet.of( + Files.createTempFile("foo", "bar").toFile(), Files.createTempFile("baz", "qux").toFile()); + } + + File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException { + return Files.createTempFile(new File("baz").toPath(), "foo", "bar").toFile(); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestInput.java new file mode 100644 index 00000000..10662a9a --- /dev/null +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestInput.java @@ -0,0 +1,96 @@ +package tech.picnic.errorprone.refasterrules; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import com.google.common.collect.BoundType; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import java.math.RoundingMode; +import java.util.EnumSet; +import java.util.stream.Stream; +import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; + +final class ImmutableEnumSetRulesTest implements RefasterRuleCollectionTestCase { + @Override + public ImmutableSet elidedTypesAndStaticImports() { + return ImmutableSet.of(EnumSet.class, toImmutableSet()); + } + + ImmutableSet> testSetsImmutableEnumSetIterable() { + return ImmutableSet.of( + ImmutableSet.copyOf(Iterables.cycle(RoundingMode.UP)), + ImmutableSet.copyOf(EnumSet.allOf(RoundingMode.class))); + } + + ImmutableSet testSetsImmutableEnumSetArraysAsList() { + return ImmutableSet.copyOf(RoundingMode.values()); + } + + ImmutableSet> testSetsImmutableEnumSet1() { + return ImmutableSet.of( + ImmutableSet.of(RoundingMode.UP), ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP))); + } + + ImmutableSet> testSetsImmutableEnumSet2() { + return ImmutableSet.of( + ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN), + ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN))); + } + + ImmutableSet> testSetsImmutableEnumSet3() { + return ImmutableSet.of( + ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING), + ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING))); + } + + ImmutableSet> testSetsImmutableEnumSet4() { + return ImmutableSet.of( + ImmutableSet.of( + RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR), + ImmutableSet.copyOf( + EnumSet.of( + RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR))); + } + + ImmutableSet> testSetsImmutableEnumSet5() { + return ImmutableSet.of( + ImmutableSet.of( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY), + ImmutableSet.copyOf( + EnumSet.of( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY))); + } + + ImmutableSet testSetsImmutableEnumSet6() { + return ImmutableSet.of( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY, + RoundingMode.HALF_EVEN); + } + + ImmutableSet testSetsImmutableEnumSetVarArgs() { + return ImmutableSet.copyOf( + EnumSet.of( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY, + RoundingMode.HALF_EVEN)); + } + + ImmutableSet testStreamToImmutableEnumSet() { + return Stream.of(BoundType.OPEN).collect(toImmutableSet()); + } +} diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestOutput.java new file mode 100644 index 00000000..c96b01db --- /dev/null +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableEnumSetRulesTestOutput.java @@ -0,0 +1,96 @@ +package tech.picnic.errorprone.refasterrules; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Sets.toImmutableEnumSet; + +import com.google.common.collect.BoundType; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import java.math.RoundingMode; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.stream.Stream; +import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; + +final class ImmutableEnumSetRulesTest implements RefasterRuleCollectionTestCase { + @Override + public ImmutableSet elidedTypesAndStaticImports() { + return ImmutableSet.of(EnumSet.class, toImmutableSet()); + } + + ImmutableSet> testSetsImmutableEnumSetIterable() { + return ImmutableSet.of( + Sets.immutableEnumSet(Iterables.cycle(RoundingMode.UP)), + Sets.immutableEnumSet(EnumSet.allOf(RoundingMode.class))); + } + + ImmutableSet testSetsImmutableEnumSetArraysAsList() { + return Sets.immutableEnumSet(Arrays.asList(RoundingMode.values())); + } + + ImmutableSet> testSetsImmutableEnumSet1() { + return ImmutableSet.of( + Sets.immutableEnumSet(RoundingMode.UP), Sets.immutableEnumSet(RoundingMode.UP)); + } + + ImmutableSet> testSetsImmutableEnumSet2() { + return ImmutableSet.of( + Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN), + Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN)); + } + + ImmutableSet> testSetsImmutableEnumSet3() { + return ImmutableSet.of( + Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING), + Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING)); + } + + ImmutableSet> testSetsImmutableEnumSet4() { + return ImmutableSet.of( + Sets.immutableEnumSet( + RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR), + Sets.immutableEnumSet( + RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR)); + } + + ImmutableSet> testSetsImmutableEnumSet5() { + return ImmutableSet.of( + Sets.immutableEnumSet( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY), + Sets.immutableEnumSet( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY)); + } + + ImmutableSet testSetsImmutableEnumSet6() { + return Sets.immutableEnumSet( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY, + RoundingMode.HALF_EVEN); + } + + ImmutableSet testSetsImmutableEnumSetVarArgs() { + return Sets.immutableEnumSet( + RoundingMode.UP, + RoundingMode.DOWN, + RoundingMode.CEILING, + RoundingMode.FLOOR, + RoundingMode.UNNECESSARY, + RoundingMode.HALF_EVEN); + } + + ImmutableSet testStreamToImmutableEnumSet() { + return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet()); + } +} diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestInput.java index 96231993..dc462c7e 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestInput.java @@ -26,6 +26,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase Stream.empty().collect(toImmutableMultiset())); } + @SuppressWarnings("unchecked") ImmutableMultiset> testIterableToImmutableMultiset() { return ImmutableMultiset.of( ImmutableList.of(1).stream().collect(toImmutableMultiset()), diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestOutput.java index 76af043a..3e4c01d0 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableMultisetRulesTestOutput.java @@ -24,6 +24,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase return ImmutableMultiset.of(ImmutableMultiset.of(), ImmutableMultiset.of()); } + @SuppressWarnings("unchecked") ImmutableMultiset> testIterableToImmutableMultiset() { return ImmutableMultiset.of( ImmutableMultiset.copyOf(ImmutableList.of(1)), diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestInput.java index 36c0b35f..7424a9b6 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestInput.java @@ -37,6 +37,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe Stream.empty().collect(toImmutableSortedMultiset(naturalOrder()))); } + @SuppressWarnings("unchecked") ImmutableMultiset> testIterableToImmutableSortedMultiset() { return ImmutableMultiset.of( ImmutableSortedMultiset.copyOf(naturalOrder(), ImmutableList.of(1)), diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestOutput.java index 36fca66a..e430c8da 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ImmutableSortedMultisetRulesTestOutput.java @@ -35,6 +35,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe return ImmutableMultiset.of(ImmutableSortedMultiset.of(), ImmutableSortedMultiset.of()); } + @SuppressWarnings("unchecked") ImmutableMultiset> testIterableToImmutableSortedMultiset() { return ImmutableMultiset.of( ImmutableSortedMultiset.copyOf(ImmutableList.of(1)), diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestInput.java index 0c398251..8abb82ce 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestInput.java @@ -32,8 +32,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase { (Runnable) () -> assertTrue(true)); } - void testThrowNewAssertionError() { - Assertions.fail(); + Object testFail() { + return Assertions.fail(); } Object testFailWithMessage() { @@ -44,8 +44,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase { return Assertions.fail("foo", new IllegalStateException()); } - void testFailWithThrowable() { - Assertions.fail(new IllegalStateException()); + Object testFailWithThrowable() { + return Assertions.fail(new IllegalStateException()); } void testAssertThatIsTrue() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestOutput.java index 99a62a38..66ce892c 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/JUnitToAssertJRulesTestOutput.java @@ -35,8 +35,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase { (Runnable) () -> assertTrue(true)); } - void testThrowNewAssertionError() { - throw new AssertionError(); + Object testFail() { + return org.assertj.core.api.Assertions.fail(); } Object testFailWithMessage() { @@ -47,8 +47,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase { return org.assertj.core.api.Assertions.fail("foo", new IllegalStateException()); } - void testFailWithThrowable() { - throw new AssertionError(new IllegalStateException()); + Object testFailWithThrowable() { + return org.assertj.core.api.Assertions.fail(new IllegalStateException()); } void testAssertThatIsTrue() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestInput.java new file mode 100644 index 00000000..256c9494 --- /dev/null +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestInput.java @@ -0,0 +1,57 @@ +package tech.picnic.errorprone.refasterrules; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.micrometer.core.instrument.Tag; +import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; + +final class MicrometerRulesTest implements RefasterRuleCollectionTestCase { + @Override + public ImmutableSet elidedTypesAndStaticImports() { + return ImmutableSet.of(ImmutableList.class); + } + + ImmutableSet> testTagsOf1() { + return ImmutableSet.of( + ImmutableSet.of(Tag.of("foo", "v1")), ImmutableList.of(Tag.of("bar", "v2"))); + } + + ImmutableSet> testTagsOf2() { + return ImmutableSet.of( + ImmutableSet.of(Tag.of("foo", "v1"), Tag.of("bar", "v2")), + ImmutableList.of(Tag.of("baz", "v3"), Tag.of("qux", "v4"))); + } + + ImmutableSet> testTagsOf3() { + return ImmutableSet.of( + ImmutableSet.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3")), + ImmutableList.of(Tag.of("qux", "v4"), Tag.of("quux", "v5"), Tag.of("corge", "v6"))); + } + + ImmutableSet> testTagsOf4() { + return ImmutableSet.of( + ImmutableSet.of( + Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3"), Tag.of("qux", "v4")), + ImmutableList.of( + Tag.of("quux", "v5"), + Tag.of("corge", "v6"), + Tag.of("grault", "v7"), + Tag.of("garply", "v8"))); + } + + ImmutableSet> testTagsOf5() { + return ImmutableSet.of( + ImmutableSet.of( + Tag.of("foo", "v1"), + Tag.of("bar", "v2"), + Tag.of("baz", "v3"), + Tag.of("qux", "v4"), + Tag.of("quux", "v5")), + ImmutableList.of( + Tag.of("corge", "v6"), + Tag.of("grault", "v7"), + Tag.of("garply", "v8"), + Tag.of("waldo", "v9"), + Tag.of("fred", "v10"))); + } +} diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestOutput.java new file mode 100644 index 00000000..74c4dd50 --- /dev/null +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/MicrometerRulesTestOutput.java @@ -0,0 +1,56 @@ +package tech.picnic.errorprone.refasterrules; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; + +final class MicrometerRulesTest implements RefasterRuleCollectionTestCase { + @Override + public ImmutableSet elidedTypesAndStaticImports() { + return ImmutableSet.of(ImmutableList.class); + } + + ImmutableSet> testTagsOf1() { + return ImmutableSet.of(Tags.of(Tag.of("foo", "v1")), Tags.of(Tag.of("bar", "v2"))); + } + + ImmutableSet> testTagsOf2() { + return ImmutableSet.of( + Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2")), + Tags.of(Tag.of("baz", "v3"), Tag.of("qux", "v4"))); + } + + ImmutableSet> testTagsOf3() { + return ImmutableSet.of( + Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3")), + Tags.of(Tag.of("qux", "v4"), Tag.of("quux", "v5"), Tag.of("corge", "v6"))); + } + + ImmutableSet> testTagsOf4() { + return ImmutableSet.of( + Tags.of(Tag.of("foo", "v1"), Tag.of("bar", "v2"), Tag.of("baz", "v3"), Tag.of("qux", "v4")), + Tags.of( + Tag.of("quux", "v5"), + Tag.of("corge", "v6"), + Tag.of("grault", "v7"), + Tag.of("garply", "v8"))); + } + + ImmutableSet> testTagsOf5() { + return ImmutableSet.of( + Tags.of( + Tag.of("foo", "v1"), + Tag.of("bar", "v2"), + Tag.of("baz", "v3"), + Tag.of("qux", "v4"), + Tag.of("quux", "v5")), + Tags.of( + Tag.of("corge", "v6"), + Tag.of("grault", "v7"), + Tag.of("garply", "v8"), + Tag.of("waldo", "v9"), + Tag.of("fred", "v10"))); + } +} diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestInput.java index 20eb7fe2..dc6ded46 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestInput.java @@ -13,6 +13,10 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(Streams.class); } + Optional testOptionalEmpty() { + return Optional.ofNullable(null); + } + ImmutableSet> testOptionalOfNullable() { return ImmutableSet.of( toString() == null ? Optional.empty() : Optional.of(toString()), @@ -79,11 +83,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return Optional.of("foo").orElseGet(() -> Optional.of("bar").orElseThrow()); } - ImmutableSet testOptionalOrElseGet() { + ImmutableSet testOptionalOrElse() { return ImmutableSet.of( - Optional.of("foo").orElse("bar"), - Optional.of("baz").orElse(toString()), - Optional.of("qux").orElse(String.valueOf(true))); + Optional.of("foo").orElseGet(() -> "bar"), Optional.of("baz").orElseGet(() -> toString())); } ImmutableSet testStreamFlatMapOptional() { @@ -120,10 +122,12 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of( Optional.of("foo").or(() -> Optional.empty()), Optional.of("bar").or(Optional::empty), - Optional.of("baz").stream().findFirst(), - Optional.of("qux").stream().findAny(), - Optional.of("quux").stream().min(String::compareTo), - Optional.of("quuz").stream().max(String::compareTo)); + Optional.of("baz").map(Optional::of).orElseGet(() -> Optional.empty()), + Optional.of("qux").map(Optional::of).orElseGet(Optional::empty), + Optional.of("quux").stream().findFirst(), + Optional.of("quuz").stream().findAny(), + Optional.of("corge").stream().min(String::compareTo), + Optional.of("grault").stream().max(String::compareTo)); } ImmutableSet> testOptionalFilter() { @@ -136,9 +140,7 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return Optional.of(1).stream().map(String::valueOf).findAny(); } - ImmutableSet> testOptionalStream() { - return ImmutableSet.of( - Optional.of("foo").map(Stream::of).orElse(Stream.empty()), - Optional.of("bar").map(Stream::of).orElseGet(Stream::empty)); + Stream testOptionalStream() { + return Optional.of("foo").map(Stream::of).orElseGet(Stream::empty); } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestOutput.java index cf589e9d..eb3af243 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/OptionalRulesTestOutput.java @@ -15,6 +15,10 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of(Streams.class); } + Optional testOptionalEmpty() { + return Optional.empty(); + } + ImmutableSet> testOptionalOfNullable() { return ImmutableSet.of(Optional.ofNullable(toString()), Optional.ofNullable(toString())); } @@ -76,11 +80,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return Optional.of("foo").or(() -> Optional.of("bar")).orElseThrow(); } - ImmutableSet testOptionalOrElseGet() { + ImmutableSet testOptionalOrElse() { return ImmutableSet.of( - Optional.of("foo").orElse("bar"), - Optional.of("baz").orElse(toString()), - Optional.of("qux").orElseGet(() -> String.valueOf(true))); + Optional.of("foo").orElse("bar"), Optional.of("baz").orElseGet(() -> toString())); } ImmutableSet testStreamFlatMapOptional() { @@ -120,7 +122,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { Optional.of("baz"), Optional.of("qux"), Optional.of("quux"), - Optional.of("quuz")); + Optional.of("quuz"), + Optional.of("corge"), + Optional.of("grault")); } ImmutableSet> testOptionalFilter() { @@ -132,7 +136,7 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase { return Optional.of(1).map(String::valueOf); } - ImmutableSet> testOptionalStream() { - return ImmutableSet.of(Optional.of("foo").stream(), Optional.of("bar").stream()); + Stream testOptionalStream() { + return Optional.of("foo").stream(); } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestInput.java index cc1773c8..419f4c9c 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestInput.java @@ -9,6 +9,8 @@ import com.google.common.primitives.Floats; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.common.primitives.Shorts; +import com.google.common.primitives.UnsignedInts; +import com.google.common.primitives.UnsignedLongs; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { @@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { Floats.class, Ints.class, Longs.class, - Shorts.class); + Shorts.class, + UnsignedInts.class, + UnsignedLongs.class); } ImmutableSet testLessThan() { @@ -105,34 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { return Doubles.hashCode(1); } - int testBooleanCompare() { - return Booleans.compare(false, true); - } - - int testCharacterCompare() { - return Chars.compare('a', 'b'); - } - - int testShortCompare() { - return Shorts.compare((short) 1, (short) 2); - } - - int testIntegerCompare() { - return Ints.compare(1, 2); - } - - int testLongCompare() { - return Longs.compare(1, 2); - } - - int testFloatCompare() { - return Floats.compare(1, 2); - } - - int testDoubleCompare() { - return Doubles.compare(1, 2); - } - int testCharacterBytes() { return Chars.BYTES; } @@ -190,4 +166,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of( Long.signum(1L) < 0, Long.signum(2L) <= -1, Long.signum(3L) >= 0, Long.signum(4L) > -1); } + + int testIntegerCompareUnsigned() { + return UnsignedInts.compare(1, 2); + } + + long testLongCompareUnsigned() { + return UnsignedLongs.compare(1, 2); + } + + int testIntegerDivideUnsigned() { + return UnsignedInts.divide(1, 2); + } + + long testLongDivideUnsigned() { + return UnsignedLongs.divide(1, 2); + } + + int testIntegerRemainderUnsigned() { + return UnsignedInts.remainder(1, 2); + } + + long testLongRemainderUnsigned() { + return UnsignedLongs.remainder(1, 2); + } + + ImmutableSet testIntegerParseUnsignedInt() { + return ImmutableSet.of(UnsignedInts.parseUnsignedInt("1"), Integer.parseUnsignedInt("2", 10)); + } + + ImmutableSet testLongParseUnsignedLong() { + return ImmutableSet.of(UnsignedLongs.parseUnsignedLong("1"), Long.parseUnsignedLong("2", 10)); + } + + int testIntegerParseUnsignedIntWithRadix() { + return UnsignedInts.parseUnsignedInt("1", 2); + } + + long testLongParseUnsignedLongWithRadix() { + return UnsignedLongs.parseUnsignedLong("1", 2); + } + + ImmutableSet testIntegerToUnsignedString() { + return ImmutableSet.of(UnsignedInts.toString(1), Integer.toUnsignedString(2, 10)); + } + + ImmutableSet testLongToUnsignedString() { + return ImmutableSet.of(UnsignedLongs.toString(1), Long.toUnsignedString(2, 10)); + } + + String testIntegerToUnsignedStringWithRadix() { + return UnsignedInts.toString(1, 2); + } + + String testLongToUnsignedStringWithRadix() { + return UnsignedLongs.toString(1, 2); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestOutput.java index cba9a1a9..f111b028 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/PrimitiveRulesTestOutput.java @@ -9,6 +9,8 @@ import com.google.common.primitives.Floats; import com.google.common.primitives.Ints; import com.google.common.primitives.Longs; import com.google.common.primitives.Shorts; +import com.google.common.primitives.UnsignedInts; +import com.google.common.primitives.UnsignedLongs; import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { @@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { Floats.class, Ints.class, Longs.class, - Shorts.class); + Shorts.class, + UnsignedInts.class, + UnsignedLongs.class); } ImmutableSet testLessThan() { @@ -105,34 +109,6 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { return Double.hashCode(1); } - int testBooleanCompare() { - return Boolean.compare(false, true); - } - - int testCharacterCompare() { - return Character.compare('a', 'b'); - } - - int testShortCompare() { - return Short.compare((short) 1, (short) 2); - } - - int testIntegerCompare() { - return Integer.compare(1, 2); - } - - int testLongCompare() { - return Long.compare(1, 2); - } - - int testFloatCompare() { - return Float.compare(1, 2); - } - - int testDoubleCompare() { - return Double.compare(1, 2); - } - int testCharacterBytes() { return Character.BYTES; } @@ -190,4 +166,60 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase { return ImmutableSet.of( Long.signum(1L) == -1, Long.signum(2L) == -1, Long.signum(3L) != -1, Long.signum(4L) != -1); } + + int testIntegerCompareUnsigned() { + return Integer.compareUnsigned(1, 2); + } + + long testLongCompareUnsigned() { + return Long.compareUnsigned(1, 2); + } + + int testIntegerDivideUnsigned() { + return Integer.divideUnsigned(1, 2); + } + + long testLongDivideUnsigned() { + return Long.divideUnsigned(1, 2); + } + + int testIntegerRemainderUnsigned() { + return Integer.remainderUnsigned(1, 2); + } + + long testLongRemainderUnsigned() { + return Long.remainderUnsigned(1, 2); + } + + ImmutableSet testIntegerParseUnsignedInt() { + return ImmutableSet.of(Integer.parseUnsignedInt("1"), Integer.parseUnsignedInt("2")); + } + + ImmutableSet testLongParseUnsignedLong() { + return ImmutableSet.of(Long.parseUnsignedLong("1"), Long.parseUnsignedLong("2")); + } + + int testIntegerParseUnsignedIntWithRadix() { + return Integer.parseUnsignedInt("1", 2); + } + + long testLongParseUnsignedLongWithRadix() { + return Long.parseUnsignedLong("1", 2); + } + + ImmutableSet testIntegerToUnsignedString() { + return ImmutableSet.of(Integer.toUnsignedString(1), Integer.toUnsignedString(2)); + } + + ImmutableSet testLongToUnsignedString() { + return ImmutableSet.of(Long.toUnsignedString(1), Long.toUnsignedString(2)); + } + + String testIntegerToUnsignedStringWithRadix() { + return Integer.toUnsignedString(1, 2); + } + + String testLongToUnsignedStringWithRadix() { + return Long.toUnsignedString(1, 2); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestInput.java index f2f04dc3..80375ea4 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestInput.java @@ -10,6 +10,7 @@ import static java.util.stream.Collectors.minBy; import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -21,7 +22,9 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import java.util.stream.Stream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.math.MathFlux; @@ -142,7 +145,7 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { } Flux testFluxTake() { - return Flux.just(1, 2, 3).take(1); + return Flux.just(1, 2, 3).take(1, true); } Mono testMonoDefaultIfEmpty() { @@ -182,8 +185,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Flux.range(0, 0)); } - Flux testFluxJust() { - return Flux.range(0, 1); + ImmutableSet> testFluxJust() { + return ImmutableSet.of(Flux.range(0, 1), Mono.just(2).repeat().take(1)); } ImmutableSet> testMonoIdentity() { @@ -207,11 +210,12 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { ImmutableSet> testFluxConcatMap() { return ImmutableSet.of( - Flux.just(1).flatMap(Mono::just, 1), - Flux.just(2).flatMapSequential(Mono::just, 1), - Flux.just(3).map(Mono::just).concatMap(identity()), - Flux.just(4).map(Mono::just).concatMap(v -> v), - Flux.just(5).map(Mono::just).concatMap(v -> Mono.empty())); + Flux.just(1).concatMap(Mono::just, 0), + Flux.just(2).flatMap(Mono::just, 1), + Flux.just(3).flatMapSequential(Mono::just, 1), + Flux.just(4).map(Mono::just).concatMap(identity()), + Flux.just(5).map(Mono::just).concatMap(v -> v), + Flux.just(6).map(Mono::just).concatMap(v -> Mono.empty())); } ImmutableSet> testFluxConcatMapWithPrefetch() { @@ -432,8 +436,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2)); } - Flux testFluxFromIterable() { - return Flux.fromStream(ImmutableList.of("foo").stream()); + ImmutableSet> testFluxFromIterable() { + return ImmutableSet.of( + Flux.fromStream(ImmutableList.of("foo")::stream), + Flux.fromStream(() -> ImmutableList.of("bar").stream())); } ImmutableSet> testFluxCountMapMathToIntExact() { @@ -592,6 +598,18 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { return StepVerifier.create(Flux.just(1)); } + Object testStepVerifierVerify() { + return Mono.empty().as(StepVerifier::create).expectError().verifyThenAssertThat(); + } + + Object testStepVerifierVerifyDuration() { + return Mono.empty().as(StepVerifier::create).expectError().verifyThenAssertThat(Duration.ZERO); + } + + StepVerifier testStepVerifierVerifyLater() { + return Mono.empty().as(StepVerifier::create).expectError().verifyLater().verifyLater(); + } + ImmutableSet> testStepVerifierStepIdentity() { return ImmutableSet.of( Mono.just(1).as(StepVerifier::create).expectNext(), @@ -632,17 +650,43 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { .verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(AssertionError.class))); } - Duration testStepVerifierLastStepVerifyErrorMatches() { - return Mono.empty() - .as(StepVerifier::create) - .expectErrorMatches(IllegalArgumentException.class::equals) - .verify(); + ImmutableSet testStepVerifierLastStepVerifyErrorMatches() { + return ImmutableSet.of( + Mono.empty() + .as(StepVerifier::create) + .expectErrorMatches(IllegalArgumentException.class::equals) + .verify(), + Mono.empty() + .as(StepVerifier::create) + .expectError() + .verifyThenAssertThat() + .hasOperatorErrorMatching(IllegalStateException.class::equals)); } Duration testStepVerifierLastStepVerifyErrorSatisfies() { return Mono.empty().as(StepVerifier::create).expectErrorSatisfies(t -> {}).verify(); } + ImmutableSet testStepVerifierLastStepVerifyErrorSatisfiesAssertJ() { + return ImmutableSet.of( + Mono.empty() + .as(StepVerifier::create) + .expectError() + .verifyThenAssertThat() + .hasOperatorErrorOfType(IllegalArgumentException.class) + .hasOperatorErrorWithMessage("foo"), + Mono.empty() + .as(StepVerifier::create) + .expectError(IllegalStateException.class) + .verifyThenAssertThat() + .hasOperatorErrorWithMessage("bar"), + Mono.empty() + .as(StepVerifier::create) + .expectErrorMessage("baz") + .verifyThenAssertThat() + .hasOperatorErrorOfType(AssertionError.class)); + } + Duration testStepVerifierLastStepVerifyErrorMessage() { return Mono.empty().as(StepVerifier::create).expectErrorMessage("foo").verify(); } @@ -650,4 +694,20 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Duration testStepVerifierLastStepVerifyTimeout() { return Mono.empty().as(StepVerifier::create).expectTimeout(Duration.ZERO).verify(); } + + Mono testMonoFromFutureSupplier() { + return Mono.fromFuture(CompletableFuture.completedFuture(null)); + } + + Mono testMonoFromFutureSupplierBoolean() { + return Mono.fromFuture(CompletableFuture.completedFuture(null), true); + } + + Mono testMonoFromFutureAsyncLoadingCacheGet() { + return Mono.fromFuture(() -> ((AsyncLoadingCache) null).get(0)); + } + + Flux testFluxFromStreamSupplier() { + return Flux.fromStream(Stream.of(1)); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestOutput.java index 6bfd98f9..37f8f4f8 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/ReactorRulesTestOutput.java @@ -12,6 +12,7 @@ import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static reactor.function.TupleUtils.function; +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -23,7 +24,9 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import java.util.stream.Stream; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.function.TupleUtils; @@ -147,7 +150,7 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { } Flux testFluxTake() { - return Flux.just(1, 2, 3).take(1, true); + return Flux.just(1, 2, 3).take(1); } Mono testMonoDefaultIfEmpty() { @@ -186,8 +189,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Flux.empty()); } - Flux testFluxJust() { - return Flux.just(0); + ImmutableSet> testFluxJust() { + return ImmutableSet.of(Flux.just(0), Flux.just(2)); } ImmutableSet> testMonoIdentity() { @@ -214,7 +217,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Flux.just(2).concatMap(Mono::just), Flux.just(3).concatMap(Mono::just), Flux.just(4).concatMap(Mono::just), - Flux.just(5).map(Mono::just).concatMap(v -> Mono.empty())); + Flux.just(5).concatMap(Mono::just), + Flux.just(6).map(Mono::just).concatMap(v -> Mono.empty())); } ImmutableSet> testFluxConcatMapWithPrefetch() { @@ -427,8 +431,9 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2)); } - Flux testFluxFromIterable() { - return Flux.fromIterable(ImmutableList.of("foo")); + ImmutableSet> testFluxFromIterable() { + return ImmutableSet.of( + Flux.fromIterable(ImmutableList.of("foo")), Flux.fromIterable(ImmutableList.of("bar"))); } ImmutableSet> testFluxCountMapMathToIntExact() { @@ -581,6 +586,18 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { return Flux.just(1).as(StepVerifier::create); } + Object testStepVerifierVerify() { + return Mono.empty().as(StepVerifier::create).expectError().verify(); + } + + Object testStepVerifierVerifyDuration() { + return Mono.empty().as(StepVerifier::create).expectError().verify(Duration.ZERO); + } + + StepVerifier testStepVerifierVerifyLater() { + return Mono.empty().as(StepVerifier::create).expectError().verifyLater(); + } + ImmutableSet> testStepVerifierStepIdentity() { return ImmutableSet.of( Mono.just(1).as(StepVerifier::create), @@ -614,16 +631,36 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Mono.empty().as(StepVerifier::create).verifyError(AssertionError.class)); } - Duration testStepVerifierLastStepVerifyErrorMatches() { - return Mono.empty() - .as(StepVerifier::create) - .verifyErrorMatches(IllegalArgumentException.class::equals); + ImmutableSet testStepVerifierLastStepVerifyErrorMatches() { + return ImmutableSet.of( + Mono.empty() + .as(StepVerifier::create) + .verifyErrorMatches(IllegalArgumentException.class::equals), + Mono.empty() + .as(StepVerifier::create) + .verifyErrorMatches(IllegalStateException.class::equals)); } Duration testStepVerifierLastStepVerifyErrorSatisfies() { return Mono.empty().as(StepVerifier::create).verifyErrorSatisfies(t -> {}); } + ImmutableSet testStepVerifierLastStepVerifyErrorSatisfiesAssertJ() { + return ImmutableSet.of( + Mono.empty() + .as(StepVerifier::create) + .verifyErrorSatisfies( + t -> assertThat(t).isInstanceOf(IllegalArgumentException.class).hasMessage("foo")), + Mono.empty() + .as(StepVerifier::create) + .verifyErrorSatisfies( + t -> assertThat(t).isInstanceOf(IllegalStateException.class).hasMessage("bar")), + Mono.empty() + .as(StepVerifier::create) + .verifyErrorSatisfies( + t -> assertThat(t).isInstanceOf(AssertionError.class).hasMessage("baz"))); + } + Duration testStepVerifierLastStepVerifyErrorMessage() { return Mono.empty().as(StepVerifier::create).verifyErrorMessage("foo"); } @@ -631,4 +668,20 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase { Duration testStepVerifierLastStepVerifyTimeout() { return Mono.empty().as(StepVerifier::create).verifyTimeout(Duration.ZERO); } + + Mono testMonoFromFutureSupplier() { + return Mono.fromFuture(() -> CompletableFuture.completedFuture(null)); + } + + Mono testMonoFromFutureSupplierBoolean() { + return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true); + } + + Mono testMonoFromFutureAsyncLoadingCacheGet() { + return Mono.fromFuture(() -> ((AsyncLoadingCache) null).get(0), true); + } + + Flux testFluxFromStreamSupplier() { + return Flux.fromStream(() -> Stream.of(1)); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestInput.java index 01cd2456..5126726c 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestInput.java @@ -120,7 +120,7 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase { Stream.of("bar").map(String::length).findFirst()); } - ImmutableSet testStreamIsEmpty() { + ImmutableSet testStreamFindAnyIsEmpty() { return ImmutableSet.of( Stream.of(1).count() == 0, Stream.of(2).count() <= 0, @@ -131,15 +131,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase { Stream.of(7).collect(collectingAndThen(toImmutableList(), ImmutableList::isEmpty)), Stream.of(8).collect(collectingAndThen(toImmutableMap(k -> k, v -> v), Map::isEmpty)), Stream.of(9) - .collect(collectingAndThen(toImmutableMap(k -> k, v -> v), ImmutableMap::isEmpty))); + .collect(collectingAndThen(toImmutableMap(k -> k, v -> v), ImmutableMap::isEmpty)), + Stream.of(10).count() != 0, + Stream.of(11).count() > 0, + Stream.of(12).count() >= 1); } - ImmutableSet testStreamIsNotEmpty() { - return ImmutableSet.of( - Stream.of(1).count() != 0, - Stream.of(2).count() > 0, - Stream.of(3).count() >= 1, - Stream.of(4).findFirst().isPresent()); + boolean testStreamFindAnyIsPresent() { + return Stream.of(1).findFirst().isPresent(); } ImmutableSet> testStreamMin() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestOutput.java index 0d865cba..a11b7ca6 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StreamRulesTestOutput.java @@ -121,7 +121,7 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase { Stream.of("bar").findFirst().map(String::length)); } - ImmutableSet testStreamIsEmpty() { + ImmutableSet testStreamFindAnyIsEmpty() { return ImmutableSet.of( Stream.of(1).findAny().isEmpty(), Stream.of(2).findAny().isEmpty(), @@ -131,15 +131,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase { Stream.of(6).findAny().isEmpty(), Stream.of(7).findAny().isEmpty(), Stream.of(8).findAny().isEmpty(), - Stream.of(9).findAny().isEmpty()); + Stream.of(9).findAny().isEmpty(), + !Stream.of(10).findAny().isEmpty(), + !Stream.of(11).findAny().isEmpty(), + !Stream.of(12).findAny().isEmpty()); } - ImmutableSet testStreamIsNotEmpty() { - return ImmutableSet.of( - Stream.of(1).findAny().isPresent(), - Stream.of(2).findAny().isPresent(), - Stream.of(3).findAny().isPresent(), - Stream.of(4).findAny().isPresent()); + boolean testStreamFindAnyIsPresent() { + return Stream.of(1).findAny().isPresent(); } ImmutableSet> testStreamMin() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestInput.java index a6b2f1cb..39476e36 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestInput.java @@ -96,4 +96,32 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase { int testUtf8EncodedLength() { return "foo".getBytes(UTF_8).length; } + + int testStringIndexOfChar() { + return "foo".substring(1).indexOf('a'); + } + + int testStringIndexOfString() { + return "foo".substring(1).indexOf("bar"); + } + + int testStringLastIndexOfChar() { + return "foo".substring(1).lastIndexOf('a'); + } + + int testStringLastIndexOfString() { + return "foo".substring(1).lastIndexOf("bar"); + } + + int testStringLastIndexOfCharWithIndex() { + return "foo".substring(0, 2).lastIndexOf('a'); + } + + int testStringLastIndexOfStringWithIndex() { + return "foo".substring(0, 2).lastIndexOf("bar"); + } + + boolean testStringStartsWith() { + return "foo".substring(1).startsWith("bar"); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestOutput.java index d5e1241a..7973d10d 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/StringRulesTestOutput.java @@ -96,4 +96,32 @@ final class StringRulesTest implements RefasterRuleCollectionTestCase { int testUtf8EncodedLength() { return Utf8.encodedLength("foo"); } + + int testStringIndexOfChar() { + return Math.max(-1, "foo".indexOf('a', 1) - 1); + } + + int testStringIndexOfString() { + return Math.max(-1, "foo".indexOf("bar", 1) - 1); + } + + int testStringLastIndexOfChar() { + return Math.max(-1, "foo".lastIndexOf('a') - 1); + } + + int testStringLastIndexOfString() { + return Math.max(-1, "foo".lastIndexOf("bar") - 1); + } + + int testStringLastIndexOfCharWithIndex() { + return "foo".lastIndexOf('a', 2 - 1); + } + + int testStringLastIndexOfStringWithIndex() { + return "foo".lastIndexOf("bar", 2 - 1); + } + + boolean testStringStartsWith() { + return "foo".startsWith("bar", 1); + } } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TestNGToAssertJRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TestNGToAssertJRulesTestOutput.java index bd43fb87..7de09576 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TestNGToAssertJRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TestNGToAssertJRulesTestOutput.java @@ -41,7 +41,7 @@ final class TestNGToAssertJRulesTest implements RefasterRuleCollectionTestCase { } void testFail() { - throw new AssertionError(); + fail(); } void testFailWithMessage() { diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestInput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestInput.java index 58bac6c5..8b22e112 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestInput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestInput.java @@ -66,6 +66,32 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase { return Instant.EPOCH.atZone(ZoneOffset.UTC).toOffsetDateTime(); } + ImmutableSet testInstantIdentity() { + return ImmutableSet.of( + Instant.EPOCH.plusMillis(1).plus(Duration.ZERO), + Instant.EPOCH.plusMillis(2).plus(0, ChronoUnit.MILLIS), + Instant.EPOCH.plusMillis(3).plusNanos(0L), + Instant.EPOCH.plusMillis(4).plusMillis(0), + Instant.EPOCH.plusMillis(5).plusSeconds(0L), + Instant.EPOCH.plusMillis(6).minus(Duration.ZERO), + Instant.EPOCH.plusMillis(7).minus(0, ChronoUnit.SECONDS), + Instant.EPOCH.plusMillis(8).minusNanos(0L), + Instant.EPOCH.plusMillis(9).minusMillis(0), + Instant.EPOCH.plusMillis(10).minusSeconds(0L), + Instant.parse(Instant.EPOCH.plusMillis(11).toString()), + Instant.EPOCH.plusMillis(12).truncatedTo(ChronoUnit.NANOS), + Instant.ofEpochSecond( + Instant.EPOCH.plusMillis(13).getEpochSecond(), Instant.EPOCH.plusMillis(13).getNano())); + } + + Instant testInstantTruncatedToMilliseconds() { + return Instant.ofEpochMilli(Instant.EPOCH.toEpochMilli()); + } + + Instant testInstantTruncatedToSeconds() { + return Instant.ofEpochSecond(Instant.EPOCH.getEpochSecond()); + } + OffsetDateTime testInstantAtOffset() { return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC); } diff --git a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestOutput.java b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestOutput.java index ee4f347b..cd739835 100644 --- a/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestOutput.java +++ b/error-prone-contrib/src/test/resources/tech/picnic/errorprone/refasterrules/TimeRulesTestOutput.java @@ -66,6 +66,31 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase { return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC); } + ImmutableSet testInstantIdentity() { + return ImmutableSet.of( + Instant.EPOCH.plusMillis(1), + Instant.EPOCH.plusMillis(2), + Instant.EPOCH.plusMillis(3), + Instant.EPOCH.plusMillis(4), + Instant.EPOCH.plusMillis(5), + Instant.EPOCH.plusMillis(6), + Instant.EPOCH.plusMillis(7), + Instant.EPOCH.plusMillis(8), + Instant.EPOCH.plusMillis(9), + Instant.EPOCH.plusMillis(10), + Instant.EPOCH.plusMillis(11), + Instant.EPOCH.plusMillis(12), + Instant.EPOCH.plusMillis(13)); + } + + Instant testInstantTruncatedToMilliseconds() { + return Instant.EPOCH.truncatedTo(ChronoUnit.MILLIS); + } + + Instant testInstantTruncatedToSeconds() { + return Instant.EPOCH.truncatedTo(ChronoUnit.SECONDS); + } + OffsetDateTime testInstantAtOffset() { return Instant.EPOCH.atOffset(ZoneOffset.UTC); } diff --git a/error-prone-experimental/pom.xml b/error-prone-experimental/pom.xml index 965e0875..cfcfb622 100644 --- a/error-prone-experimental/pom.xml +++ b/error-prone-experimental/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT error-prone-experimental diff --git a/error-prone-guidelines/pom.xml b/error-prone-guidelines/pom.xml index a283915f..d6299bf1 100644 --- a/error-prone-guidelines/pom.xml +++ b/error-prone-guidelines/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT error-prone-guidelines diff --git a/error-prone-utils/pom.xml b/error-prone-utils/pom.xml index 5b7fedb1..e3fd511d 100644 --- a/error-prone-utils/pom.xml +++ b/error-prone-utils/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT error-prone-utils diff --git a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/Flags.java b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/Flags.java index df2787c7..b8e26340 100644 --- a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/Flags.java +++ b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/Flags.java @@ -1,6 +1,7 @@ package tech.picnic.errorprone.utils; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.ErrorProneFlags; /** Helper methods for working with {@link ErrorProneFlags}. */ @@ -19,4 +20,19 @@ public final class Flags { ImmutableList list = errorProneFlags.getListOrEmpty(name); return list.equals(ImmutableList.of("")) ? ImmutableList.of() : list; } + + /** + * Returns the set of (comma-separated) arguments passed using the given Error Prone flag. + * + * @param errorProneFlags The full set of flags provided. + * @param name The name of the flag of interest. + * @return A non-{@code null} set of provided arguments; this set is empty if the flag was not + * provided, or if the flag's value is the empty string. + * @implNote This method does not delegate to {@link ErrorProneFlags#getSetOrEmpty(String)}, as + * that method wouldn't allow us to identify a non-singleton set of empty strings; such a set + * should not be treated as empty. + */ + public static ImmutableSet getSet(ErrorProneFlags errorProneFlags, String name) { + return ImmutableSet.copyOf(getList(errorProneFlags, name)); + } } diff --git a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/MoreASTHelpers.java b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/MoreASTHelpers.java index 30c12d3c..0caa0002 100644 --- a/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/MoreASTHelpers.java +++ b/error-prone-utils/src/main/java/tech/picnic/errorprone/utils/MoreASTHelpers.java @@ -78,4 +78,16 @@ public final class MoreASTHelpers { public static boolean areSameType(Tree treeA, Tree treeB, VisitorState state) { return ASTHelpers.isSameType(ASTHelpers.getType(treeA), ASTHelpers.getType(treeB), state); } + + /** + * Tells whether the given tree is of type {@link String}. + * + * @param tree The tree of interest. + * @param state The {@link VisitorState} describing the context in which the given tree was found. + * @return Whether the specified tree has the same type as {@link + * com.sun.tools.javac.code.Symtab#stringType}. + */ + public static boolean isStringTyped(Tree tree, VisitorState state) { + return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state); + } } diff --git a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/FlagsTest.java b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/FlagsTest.java index bc7658ce..dcc217c8 100644 --- a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/FlagsTest.java +++ b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/FlagsTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.ErrorProneOptions; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; @@ -11,21 +12,29 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; final class FlagsTest { - private static Stream getListTestCases() { - /* { args, flag, expected } */ + private static Stream getCollectionTestCases() { + /* { args, flag, listed } */ return Stream.of( arguments(ImmutableList.of(), "Foo", ImmutableList.of()), arguments(ImmutableList.of("-XepOpt:Foo=bar,baz"), "Qux", ImmutableList.of()), arguments(ImmutableList.of("-XepOpt:Foo="), "Foo", ImmutableList.of()), arguments(ImmutableList.of("-XepOpt:Foo=bar"), "Foo", ImmutableList.of("bar")), + arguments(ImmutableList.of("-XepOpt:Foo=bar,bar"), "Foo", ImmutableList.of("bar", "bar")), arguments(ImmutableList.of("-XepOpt:Foo=bar,baz"), "Foo", ImmutableList.of("bar", "baz")), arguments(ImmutableList.of("-XepOpt:Foo=,"), "Foo", ImmutableList.of("", ""))); } - @MethodSource("getListTestCases") + @MethodSource("getCollectionTestCases") @ParameterizedTest - void getList(ImmutableList args, String flag, ImmutableList expected) { + void getList(ImmutableList args, String flag, ImmutableList listed) { assertThat(Flags.getList(ErrorProneOptions.processArgs(args).getFlags(), flag)) - .containsExactlyElementsOf(expected); + .containsExactlyElementsOf(listed); + } + + @MethodSource("getCollectionTestCases") + @ParameterizedTest + void getSet(ImmutableList args, String flag, ImmutableList listed) { + assertThat(Flags.getSet(ErrorProneOptions.processArgs(args).getFlags(), flag)) + .containsExactlyElementsOf(ImmutableSet.copyOf(listed)); } } diff --git a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java index efd0a33b..905023bb 100644 --- a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java +++ b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/MoreASTHelpersTest.java @@ -9,10 +9,13 @@ import com.google.errorprone.CompilationTestHelper; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.ExpressionStatementTreeMatcher; +import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; import com.google.errorprone.bugpatterns.BugChecker.ReturnTreeMatcher; +import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; import com.google.errorprone.matchers.Description; import com.sun.source.tree.ExpressionStatementTree; +import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ReturnTree; import com.sun.source.tree.Tree; @@ -137,6 +140,25 @@ final class MoreASTHelpersTest { .doTest(); } + @Test + void isStringTyped() { + CompilationTestHelper.newInstance(IsStringTypedTestChecker.class, getClass()) + .addSourceLines( + "A.java", + "class A {", + " void m() {", + " int foo = 1;", + " // BUG: Diagnostic contains:", + " String s = \"foo\";", + "", + " hashCode();", + " // BUG: Diagnostic contains:", + " toString();", + " }", + "}") + .doTest(); + } + private static String createMethodSearchDiagnosticsMessage( BiFunction valueFunction, VisitorState state) { return Maps.toMap(ImmutableSet.of("foo", "bar", "baz"), key -> valueFunction.apply(key, state)) @@ -224,4 +246,28 @@ final class MoreASTHelpersTest { : Description.NO_MATCH; } } + + /** + * A {@link BugChecker} that delegates to {@link MoreASTHelpers#isStringTyped(Tree, + * VisitorState)}. + */ + @BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR) + public static final class IsStringTypedTestChecker extends BugChecker + implements MethodInvocationTreeMatcher, VariableTreeMatcher { + private static final long serialVersionUID = 1L; + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + return getDescription(tree, state); + } + + @Override + public Description matchVariable(VariableTree tree, VisitorState state) { + return getDescription(tree, state); + } + + private Description getDescription(Tree tree, VisitorState state) { + return MoreASTHelpers.isStringTyped(tree, state) ? describeMatch(tree) : Description.NO_MATCH; + } + } } diff --git a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/ThirdPartyLibraryTest.java b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/ThirdPartyLibraryTest.java index ac713c55..06bfd5c5 100644 --- a/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/ThirdPartyLibraryTest.java +++ b/error-prone-utils/src/test/java/tech/picnic/errorprone/utils/ThirdPartyLibraryTest.java @@ -73,7 +73,7 @@ final class ThirdPartyLibraryTest { } @ParameterizedTest - @ValueSource(booleans = {true, false}) + @ValueSource(booleans = {false, true}) void isIntroductionAllowedIgnoreClasspathCompat(boolean ignoreClassPath) { CompilationTestHelper.newInstance(IsIntroductionAllowedTestChecker.class, getClass()) .setArgs("-XepOpt:ErrorProneSupport:IgnoreClasspathCompat=" + ignoreClassPath) diff --git a/integration-tests/checkstyle-expected-changes.patch b/integration-tests/checkstyle-expected-changes.patch index 412e42f4..efb017e9 100644 --- a/integration-tests/checkstyle-expected-changes.patch +++ b/integration-tests/checkstyle-expected-changes.patch @@ -53686,6 +53686,15 @@ final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory(); final Path path = Paths.get(XdocUtil.DIRECTORY_PATH + "/config.xml"); +@@ -1081,7 +1083,7 @@ public class XdocsPagesTest { + Optional.ofNullable(field) + .map(nonNullField -> nonNullField.getAnnotation(XdocsPropertyType.class)) + .map(propertyType -> propertyType.value().getDescription()) +- .orElse(fieldClass.getSimpleName()); ++ .orElseGet(fieldClass::getSimpleName); + final String expectedValue = + getModulePropertyExpectedValue(sectionName, propertyName, field, fieldClass, instance); + @@ -1364,7 +1366,7 @@ public class XdocsPagesTest { final Object[] array = (Object[]) value; valuesStream = Arrays.stream(array); diff --git a/integration-tests/checkstyle.sh b/integration-tests/checkstyle.sh index 4c1ca56e..d238b262 100755 --- a/integration-tests/checkstyle.sh +++ b/integration-tests/checkstyle.sh @@ -3,32 +3,34 @@ set -e -u -o pipefail test_name="$(basename "${0}" .sh)" -project=checkstyle -repository=https://github.com/checkstyle/checkstyle.git -revision=checkstyle-10.14.0 +project='checkstyle' +repository='https://github.com/checkstyle/checkstyle.git' +revision='checkstyle-10.14.0' +# XXX: Configure Renovate to manage the AssertJ version declared here. +additional_build_flags='-Dassertj.version=3.24.2' +additional_source_directories='${project.basedir}${file.separator}src${file.separator}it${file.separator}java,${project.basedir}${file.separator}src${file.separator}xdocs-examples${file.separator}java' +patch_error_prone_flags='' +validation_error_prone_flags='' +# Validation skips some tests: +# - The `metadataFilesGenerationAllFiles` test is skipped because it makes line +# number assertions that will fail when the code is formatted or patched. +# - The `allCheckSectionJavaDocs` test is skipped because it validates that +# Javadoc has certain closing tags that are removed by Google Java Format. +validation_build_flags='-Dtest=!MetadataGeneratorUtilTest#metadataFilesGenerationAllFiles,!XdocsJavaDocsTest#allCheckSectionJavaDocs' if [ "${#}" -gt 2 ] || ([ "${#}" = 2 ] && [ "${1:---sync}" != '--sync' ]); then - echo "Usage: ${0} [--sync] []" + >&2 echo "Usage: ${0} [--sync] []" exit 1 fi -do_sync="$([ "${#}" = 0 ] || [ "${1:-}" != '--sync' ] || echo 1)" -report_directory="$([ "${#}" = 0 ] || ([ -z "${do_sync}" ] && echo "${1}") || ([ "${#}" = 1 ] || echo "${2}"))" -# XXX: Configure Renovate to manage the AssertJ version declared here. -build_flags="-Dassertj.version=3.24.2" -additional_src_directories="\${project.basedir}\${file.separator}src\${file.separator}it\${file.separator}java,\${project.basedir}\${file.separator}src\${file.separator}xdocs-examples\${file.separator}java" - -validation_mvn_flags="-Dtest=!MetadataGeneratorUtilTest#metadataFilesGenerationAllFiles,!XdocsJavaDocsTest#allCheckSectionJavaDocs" - -"$(dirname "${0}")"/run-integration-test.sh \ - "${test_name}" \ - "${project}" \ - "${repository}" \ - "${revision}" \ - "${build_flags}" \ - "${additional_src_directories}" \ - "" \ - "" \ - "${validation_mvn_flags}" \ - "${do_sync}" \ - "${report_directory}" +"$(dirname "${0}")/run-integration-test.sh" \ + "${test_name}" \ + "${project}" \ + "${repository}" \ + "${revision}" \ + "${additional_build_flags}" \ + "${additional_source_directories}" \ + "${patch_error_prone_flags}" \ + "${validation_error_prone_flags}" \ + "${validation_build_flags}" \ + $@ diff --git a/integration-tests/run-integration-test.sh b/integration-tests/run-integration-test.sh index f02be091..80ec95a1 100755 --- a/integration-tests/run-integration-test.sh +++ b/integration-tests/run-integration-test.sh @@ -1,7 +1,15 @@ #!/usr/bin/env bash +<<<<<<< HEAD # This script is not meant to be invoked manually, instead it should be invoked # through one of the integration test scripts such as the metrics or checkstyle one. +======= +# Integration test framework for Maven builds. +# +# This script is not meant to be invoked manually. Instead it should be invoked +# through one of the top-level integration test scripts, such as +# `checkstyle.sh`. +>>>>>>> master set -e -u -o pipefail @@ -9,8 +17,13 @@ integration_test_root="$(cd "$(dirname -- "${0}")" && pwd)" error_prone_support_root="${integration_test_root}/.." repos_root="${integration_test_root}/.repos" +<<<<<<< HEAD if [ "${#}" -ne 11 ]; then >&2 echo "Usage $(basename "${0}") [TestName] [Project] [Repository] [Revision] [BuildFlags] [AdditionalSourceDirectories] [PatchFlags] [ValidationEpFlags] [ValidationMvnFlags] [DoSync] [ReportDirectory]" +======= +if [ "${#}" -lt 9 ] || [ "${#}" -gt 11 ] || ([ "${#}" = 11 ] && [ "${10:---sync}" != '--sync' ]); then + >&2 echo "Usage: $(basename "${0}") [--sync] []" +>>>>>>> master exit 1 fi @@ -18,6 +31,7 @@ test_name="${1}" project="${2}" repository="${3}" revision="${4}" +<<<<<<< HEAD build_flags="${5}" additional_src_directories="${6}" patch_flags="${7}" @@ -25,6 +39,15 @@ validation_ep_flags="${8}" validation_mvn_flags="${9}" do_sync="${10}" report_directory="${11}" +======= +additional_build_flags="${5}" +additional_source_directories="${6}" +patch_error_prone_flags="${7}" +validation_error_prone_flags="${8}" +validation_build_flags="${9}" +do_sync="$([ "${#}" = 9 ] || [ "${10:-}" != '--sync' ] || echo 1)" +report_directory="$([ "${#}" = 9 ] || ([ -z "${do_sync}" ] && echo "${10}") || ([ "${#}" = 10 ] || echo "${11}"))" +>>>>>>> master if [ -n "${report_directory}" ]; then mkdir -p "${report_directory}" @@ -48,7 +71,6 @@ case "$(uname -s)" in ;; esac -# XXX: Configure Renovate to manage the AssertJ version declared here. shared_build_flags=" -Perror-prone-compile,error-prone-test-compile -Derror-prone.version=$( @@ -57,8 +79,13 @@ shared_build_flags=" -Derror-prone-support.version=$( mvn -f "${error_prone_support_root}" help:evaluate -Dexpression=project.version -q -DforceStdout ) +<<<<<<< HEAD -DadditionalSourceDirectories=${additional_src_directories} ${build_flags} +======= + -DadditionalSourceDirectories=${additional_source_directories} + ${additional_build_flags} +>>>>>>> master " # XXX: Configure Renovate to manage the fmt-maven-plugin version declared here. @@ -77,18 +104,18 @@ error_prone_patch_flags="${error_prone_shared_flags} -XepPatchLocation:IN_PLACE -print0 \ | xargs -0 "${grep_command}" -hoP '[^.]+$' \ | paste -s -d ',' - -) ${patch_flags}" +) ${patch_error_prone_flags}" error_prone_validation_flags="${error_prone_shared_flags} -XepDisableAllChecks $( - find "${error_prone_support_root}" \ - -path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \ - -not -path "*/error-prone-experimental/*" \ - -not -path "*/error-prone-guidelines/*" \ - -print0 \ + find "${error_prone_support_root}" \ + -path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \ + -not -path "*/error-prone-experimental/*" \ + -not -path "*/error-prone-guidelines/*" \ + -print0 \ | xargs -0 "${grep_command}" -hoP '[^.]+$' \ | "${sed_command}" -r 's,(.*),-Xep:\1:WARN,' \ | paste -s -d ' ' - -) ${validation_ep_flags}" +) ${validation_error_prone_flags}" echo "Shared build flags: ${shared_build_flags}" echo "Error Prone patch flags: ${error_prone_patch_flags}" @@ -120,8 +147,8 @@ pushd "${project_root}" # Make sure that Git is sufficiently configured to enable committing to the # project's Git repository. -git config user.email || git config user.email "integration-test@example.com" -git config user.name || git config user.name "Integration Test" +git config user.email || git config user.email 'integration-test@example.com' +git config user.name || git config user.name 'Integration Test' # Prepare the code for analysis by (a) applying the minimal set of changes # required to run Error Prone with Error Prone Support and (b) formatting the @@ -164,16 +191,12 @@ apply_patch '' # Run one more full build and log the output. # # By also running the tests, we validate that the (majority of) applied changes -# are behavior preserving. Some tests are skipped: -# - The `metadataFilesGenerationAllFiles` test is skipped because it makes line -# number assertions that will fail when the code is formatted or patched. -# - The `allCheckSectionJavaDocs` test is skipped because is validates that -# Javadoc has certain closing tags that are removed by Google Java Format. +# are behavior preserving. validation_build_log="${report_directory}/${test_name}-validation-build-log.txt" mvn ${shared_build_flags} \ clean package \ -Derror-prone.configuration-args="${error_prone_validation_flags}" \ - ${validation_mvn_flags} \ + ${validation_build_flags} \ | tee "${validation_build_log}" \ || failure=1 diff --git a/pom.xml b/pom.xml index 9194b9f7..c3d3e4d0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT pom Picnic :: Error Prone Support @@ -111,6 +111,9 @@ but also prevents excessive memory usage by heavily parallelized local builds. --> -Xmx${argLine.xmx} + + -javaagent:${org.mockito:mockito-core:jar} - 2024-03-15T12:04:44Z + 2024-11-03T15:58:19Z UTF-8 1.1.1 - 1.10.4 + 1.11.0 ${version.error-prone-orig} v${version.error-prone-orig}-picnic-1 - 2.26.1 - 0.1.22 + 2.35.1 + 0.1.28 1.0 17 - 3.9.5 - 5.11.0 + 3.9.9 + 5.14.2 1.0.1 - 0.10.24 + 0.12.1 1.1.4 - 1.6.3 + 1.17.0 3.2.3 @@ -296,10 +299,15 @@ com.fasterxml.jackson jackson-bom - 2.17.0 + 2.18.1 pom import + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + com.google.auto auto-common @@ -328,7 +336,7 @@ com.google.googlejavaformat google-java-format - 1.21.0 + 1.24.0 com.google.guava @@ -338,14 +346,14 @@ com.google.guava guava-bom - 33.0.0-jre + 33.3.1-jre pom import com.google.truth truth - 1.4.2 + 1.4.4 com.jakewharton.nopen @@ -357,10 +365,15 @@ nullaway ${version.nullaway} + + io.micrometer + micrometer-core + 1.13.6 + io.projectreactor reactor-bom - 2023.0.4 + 2023.0.11 pom import @@ -372,17 +385,22 @@ io.swagger swagger-annotations - 1.6.13 + 1.6.14 io.swagger.core.v3 swagger-annotations - 2.2.20 + 2.2.25 jakarta.servlet jakarta.servlet-api - 6.0.0 + 6.1.0 + + + javax.annotation + javax.annotation-api + 1.3.2 javax.inject @@ -407,7 +425,7 @@ net.bytebuddy byte-buddy - 1.14.12 + 1.15.10 + + org.openrewrite + rewrite-java-17 + 8.40.0 org.openrewrite @@ -476,43 +501,54 @@ org.openrewrite.recipe rewrite-recipe-bom - 2.8.1 + 2.21.1 pom import org.slf4j slf4j-bom - 2.0.12 + 2.0.16 pom import org.springframework spring-framework-bom - 6.1.5 + 6.1.14 pom import org.springframework.boot spring-boot-test - 3.2.3 + 3.3.5 org.springframework.security spring-security-bom - 6.2.2 + 6.3.4 pom import org.testng testng - 7.9.0 + 7.10.2 + + + + org.mockito + mockito-core + test + + @@ -520,7 +556,7 @@ com.github.ekryd.sortpom sortpom-maven-plugin - 3.4.1 + 4.0.0 false ${project.build.sourceEncoding} @@ -557,7 +593,7 @@ com.spotify.fmt fmt-maven-plugin - 2.22.1 + 2.25 ${basedir}/src/test/resources @@ -577,7 +613,7 @@ de.thetaphi forbiddenapis - 3.6 + 3.8 jdk-internal @@ -624,7 +660,7 @@ io.github.git-commit-id git-commit-id-maven-plugin - 8.0.2 + 9.0.1 true true @@ -643,7 +679,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.3.1 + 3.6.0 false + + + + compile-recipes + + compile + + + + + -Xlint:-options + + 8 + 8 + + **/*Recipes.java + + ${project.build.directory}/openrewrite-recipes + + + org.apache.maven.plugins maven-dependency-plugin - 3.6.1 + 3.8.1 + + **/*Recipe$*.class + **/*Recipe.class + **/*Recipes.class + + + + + + create-openrewrite-recipes-jar + + jar + + + ${project.build.directory}/openrewrite-recipes + recipes + + **/*Recipe$*.class + **/*Recipe.class + **/*Recipes.class + + + create-test-jar @@ -1114,7 +1227,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.11.1 --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED @@ -1143,7 +1256,7 @@ org.apache.maven.plugins maven-release-plugin - 3.0.1 + 3.1.1 true release @@ -1165,12 +1278,12 @@ org.apache.maven.plugins maven-site-plugin - 3.12.1 + 3.21.0 org.apache.maven.plugins maven-source-plugin - 3.3.0 + 3.3.1 generate-source-jar @@ -1184,7 +1297,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.2 **/*Test.java @@ -1197,11 +1310,6 @@ true - - org.codehaus.mojo - build-helper-maven-plugin - 3.5.0 - org.codehaus.mojo license-maven-plugin @@ -1296,6 +1404,7 @@ GPL-2.0-with-classpath-exception | CDDL/GPLv2+CE + | CDDL + GPLv2 with classpath exception | GNU General Public License, version 2 (GPL2), with the classpath exception | GNU General Public License, version 2, with the Classpath Exception | GPL2 w/ CPE @@ -1330,7 +1439,7 @@ org.codehaus.mojo tidy-maven-plugin - 1.2.0 + 1.3.0 check-pom @@ -1343,7 +1452,7 @@ org.codehaus.mojo versions-maven-plugin - 2.16.2 + 2.17.1 never @@ -1351,7 +1460,7 @@ org.gaul modernizer-maven-plugin - 2.8.0 + 2.9.0 @@ -1466,7 +1575,7 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 3.11.0.3922 + 5.0.0.4389 @@ -1475,6 +1584,10 @@ io.github.git-commit-id git-commit-id-maven-plugin + + org.apache.maven.plugins + maven-dependency-plugin + org.apache.maven.plugins maven-source-plugin @@ -1854,6 +1967,12 @@ -Xep:BetaApi:OFF + + -Xep:IdentifierName:OFF -Xep:Java7ApiChecker:OFF diff --git a/refaster-compiler/pom.xml b/refaster-compiler/pom.xml index e88dcac0..2946dbb4 100644 --- a/refaster-compiler/pom.xml +++ b/refaster-compiler/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT refaster-compiler diff --git a/refaster-compiler/src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompilerTaskListener.java b/refaster-compiler/src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompilerTaskListener.java index f62efde1..e9cd1a44 100644 --- a/refaster-compiler/src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompilerTaskListener.java +++ b/refaster-compiler/src/main/java/tech/picnic/errorprone/refaster/plugin/RefasterRuleCompilerTaskListener.java @@ -21,7 +21,6 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.PackageSymbol; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.Name; import java.io.IOException; import java.io.ObjectOutput; import java.io.ObjectOutputStream; @@ -112,7 +111,7 @@ final class RefasterRuleCompilerTaskListener implements TaskListener { return (sym != null && sym.getQualifiedName() .contentEquals(BeforeTemplate.class.getCanonicalName())) - || super.visitAnnotation(node, unused); + || super.visitAnnotation(node, null); } @Override @@ -137,10 +136,10 @@ final class RefasterRuleCompilerTaskListener implements TaskListener { return enclosingPackage == null ? "" : enclosingPackage.toString(); } - private static CharSequence toSimpleFlatName(ClassSymbol symbol) { - Name flatName = symbol.flatName(); + private static String toSimpleFlatName(ClassSymbol symbol) { + String flatName = symbol.flatName().toString(); int lastDot = flatName.lastIndexOf((byte) '.'); - return lastDot < 0 ? flatName : flatName.subSequence(lastDot + 1, flatName.length()); + return lastDot < 0 ? flatName : flatName.substring(lastDot + 1); } private static void outputCodeTransformer(CodeTransformer codeTransformer, FileObject target) diff --git a/refaster-runner/pom.xml b/refaster-runner/pom.xml index ddacd89c..459a314e 100644 --- a/refaster-runner/pom.xml +++ b/refaster-runner/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT refaster-runner @@ -58,6 +58,11 @@ guava provided + + javax.annotation + javax.annotation-api + test + javax.inject javax.inject diff --git a/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java b/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java index 274b9b57..94abfc23 100644 --- a/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java +++ b/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java @@ -166,7 +166,7 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat "Refaster Rule", description.getLink(), String.join(": ", description.checkName, description.getRawMessage())) - .overrideSeverity(severityOverride.orElse(description.severity())) + .overrideSeverity(severityOverride.orElseGet(description::severity)) .addAllFixes(description.fixes) .build(); } diff --git a/refaster-support/pom.xml b/refaster-support/pom.xml index 2f8104db..3176949f 100644 --- a/refaster-support/pom.xml +++ b/refaster-support/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT refaster-support @@ -88,10 +88,5 @@ junit-jupiter-params test - - org.mockito - mockito-core - test - diff --git a/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputation.java b/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/RequiresComputation.java similarity index 61% rename from refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputation.java rename to refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/RequiresComputation.java index dbf56e80..f4a7e077 100644 --- a/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputation.java +++ b/refaster-support/src/main/java/tech/picnic/errorprone/refaster/matchers/RequiresComputation.java @@ -2,6 +2,7 @@ package tech.picnic.errorprone.refaster.matchers; import com.google.errorprone.VisitorState; import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ArrayAccessTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; @@ -9,34 +10,19 @@ import com.sun.source.tree.LambdaExpressionTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MemberReferenceTree; import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; -/** A matcher of expressions that likely require little to no computation. */ -public final class IsLikelyTrivialComputation implements Matcher { +/** A matcher of expressions that may a non-trivial amount of computation. */ +public final class RequiresComputation implements Matcher { private static final long serialVersionUID = 1L; - /** Instantiates a new {@link IsLikelyTrivialComputation} instance. */ - public IsLikelyTrivialComputation() {} + /** Instantiates a new {@link RequiresComputation} instance. */ + public RequiresComputation() {} @Override public boolean matches(ExpressionTree expressionTree, VisitorState state) { - if (expressionTree instanceof MethodInvocationTree methodInvocation) { - // XXX: Method invocations are generally *not* trivial computations, but we make an exception - // for nullary method invocations on the result of a trivial computation. This exception - // allows this `Matcher` to by the `OptionalOrElseGet` Refaster rule, such that it does not - // suggest the introduction of lambda expressions that are better expressed as method - // references. Once the `MethodReferenceUsage` bug checker is production-ready, this exception - // should be removed. (But at that point, instead defining a `RequiresComputation` matcher may - // be more appropriate.) - if (methodInvocation.getArguments().isEmpty() - && matches(methodInvocation.getMethodSelect())) { - return true; - } - } - return matches(expressionTree); } @@ -44,11 +30,11 @@ public final class IsLikelyTrivialComputation implements Matcher // Depending on feedback such trees may be matched in the future. private static boolean matches(ExpressionTree expressionTree) { if (expressionTree instanceof ArrayAccessTree arrayAccess) { - return matches(arrayAccess.getExpression()) && matches(arrayAccess.getIndex()); + return matches(arrayAccess.getExpression()) || matches(arrayAccess.getIndex()); } if (expressionTree instanceof LiteralTree) { - return true; + return false; } if (expressionTree instanceof LambdaExpressionTree) { @@ -56,11 +42,14 @@ public final class IsLikelyTrivialComputation implements Matcher * Lambda expressions encapsulate computations, but their definition does not involve * significant computation. */ - return true; + return false; } if (expressionTree instanceof IdentifierTree) { - return true; + // XXX: Generally identifiers don't by themselves represent a computation, though they may be + // a stand-in for one if they are a Refaster template method argument. Can we identify such + // cases, also when the `Matcher` is invoked by Refaster? + return false; } if (expressionTree instanceof MemberReferenceTree memberReference) { @@ -80,11 +69,11 @@ public final class IsLikelyTrivialComputation implements Matcher } if (expressionTree instanceof UnaryTree unary) { - // XXX: Arguably side-effectful options such as pre- and post-increment and -decrement are not - // trivial. + // XXX: Arguably side-effectful options such as pre- and post-increment and -decrement + // represent non-trivial computations. return matches(unary.getExpression()); } - return false; + return ASTHelpers.constValue(expressionTree) == null; } } diff --git a/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputationTest.java b/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/RequiresComputationTest.java similarity index 72% rename from refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputationTest.java rename to refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/RequiresComputationTest.java index a140e724..ef212bdc 100644 --- a/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/IsLikelyTrivialComputationTest.java +++ b/refaster-support/src/test/java/tech/picnic/errorprone/refaster/matchers/RequiresComputationTest.java @@ -8,54 +8,69 @@ import com.google.errorprone.bugpatterns.BugChecker; import com.sun.source.tree.ReturnTree; import org.junit.jupiter.api.Test; -final class IsLikelyTrivialComputationTest { +final class RequiresComputationTest { @Test void matches() { CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass()) .addSourceLines( "A.java", + "import java.io.OutputStream;", + "import java.util.Comparator;", "import java.util.function.Predicate;", "", "class A {", - " String negative1() {", - " return String.valueOf(1);", + " int negative1() {", + " int[] arr = new int[0];", + " return arr[0];", " }", "", " String negative2() {", - " return toString().toString();", + " return null;", " }", "", - " String negative3() {", - " return \"foo\" + toString();", + " boolean negative3() {", + " return false;", " }", "", - " byte negative4() {", - " return \"foo\".getBytes()[0];", + " int negative4() {", + " return 0;", " }", "", - " int negative5() {", - " int[] arr = new int[0];", - " return arr[hashCode()];", + " String negative5() {", + " return \"foo\" + \"bar\";", " }", "", - " int negative6() {", - " return 1 * 2;", + " Predicate negative6() {", + " return v -> \"foo\".equals(v);", " }", "", - " Predicate negative7() {", - " return toString()::equals;", + " A negative7() {", + " return this;", " }", "", - " String negative8() {", - " return (toString());", + " Predicate negative8() {", + " return \"foo\"::equals;", " }", "", - " Object negative9() {", - " return (Object) toString();", + " OutputStream negative9() {", + " return System.out;", " }", "", - " int negative10() {", - " return -hashCode();", + " A negative10() {", + " return (this);", + " }", + "", + " Object negative11() {", + " return (Object) this;", + " }", + "", + " boolean negative12() {", + " boolean[] arr = new boolean[0];", + " return !arr[0];", + " }", + "", + " String negative13() {", + " return \"foo\" + 0;", " }", "", " String positive1() {", @@ -68,68 +83,63 @@ final class IsLikelyTrivialComputationTest { " return this.toString();", " }", "", - " int positive3() {", - " int[] arr = new int[0];", + " String positive3() {", " // BUG: Diagnostic contains:", - " return arr[0];", + " return String.valueOf(1);", " }", "", " String positive4() {", " // BUG: Diagnostic contains:", - " return null;", + " return toString().toString();", " }", "", - " boolean positive5() {", + " String positive5() {", " // BUG: Diagnostic contains:", - " return false;", + " return \"foo\" + toString();", " }", "", - " int positive6() {", + " byte positive6() {", " // BUG: Diagnostic contains:", - " return 0;", + " return \"foo\".getBytes()[0];", " }", "", - " String positive7() {", + " int positive7() {", + " int[] arr = new int[0];", " // BUG: Diagnostic contains:", - " return \"foo\" + \"bar\";", + " return arr[hashCode()];", " }", "", " Predicate positive8() {", " // BUG: Diagnostic contains:", - " return v -> \"foo\".equals(v);", + " return toString()::equals;", " }", "", - " A positive9() {", + " Comparator positive9() {", " // BUG: Diagnostic contains:", - " return this;", + " return toString().CASE_INSENSITIVE_ORDER;", " }", "", - " Predicate positive10() {", + " String positive10() {", " // BUG: Diagnostic contains:", - " return \"foo\"::equals;", + " return (toString());", " }", "", - " A positive11() {", + " Object positive11() {", " // BUG: Diagnostic contains:", - " return (this);", + " return (Object) toString();", " }", "", - " Object positive12() {", + " int positive12() {", " // BUG: Diagnostic contains:", - " return (Object) this;", - " }", - "", - " boolean positive13() {", - " // BUG: Diagnostic contains:", - " return !false;", + " return -hashCode();", " }", "}") .doTest(); } - /** A {@link BugChecker} that simply delegates to {@link IsLikelyTrivialComputation}. */ + /** A {@link BugChecker} that simply delegates to {@link RequiresComputation}. */ @BugPattern( - summary = "Flags return statement expressions matched by `IsLikelyTrivialComputation`", + summary = "Flags return statement expressions matched by `RequiresComputation`", severity = ERROR) public static final class MatcherTestChecker extends AbstractMatcherTestChecker { private static final long serialVersionUID = 1L; @@ -141,7 +151,7 @@ final class IsLikelyTrivialComputationTest { super( (expressionTree, state) -> state.getPath().getParentPath().getLeaf() instanceof ReturnTree - && new IsLikelyTrivialComputation().matches(expressionTree, state)); + && new RequiresComputation().matches(expressionTree, state)); } } } diff --git a/refaster-test-support/pom.xml b/refaster-test-support/pom.xml index 0bbfb907..89b7795c 100644 --- a/refaster-test-support/pom.xml +++ b/refaster-test-support/pom.xml @@ -5,7 +5,7 @@ tech.picnic.error-prone-support error-prone-support - 0.16.2-SNAPSHOT + 0.19.2-SNAPSHOT refaster-test-support @@ -50,6 +50,11 @@ com.google.guava guava + + javax.annotation + javax.annotation-api + test + javax.inject javax.inject diff --git a/refaster-test-support/src/main/java/tech/picnic/errorprone/refaster/test/RefasterRuleCollection.java b/refaster-test-support/src/main/java/tech/picnic/errorprone/refaster/test/RefasterRuleCollection.java index 7f95b39b..99673e20 100644 --- a/refaster-test-support/src/main/java/tech/picnic/errorprone/refaster/test/RefasterRuleCollection.java +++ b/refaster-test-support/src/main/java/tech/picnic/errorprone/refaster/test/RefasterRuleCollection.java @@ -61,6 +61,9 @@ import tech.picnic.errorprone.refaster.runner.Refaster; // XXX: This check currently only validates that one `Refaster.anyOf` branch in one // `@BeforeTemplate` method is covered by a test. Review how we can make sure that _all_ // `@BeforeTemplate` methods and `Refaster.anyOf` branches are covered. +// XXX: Look into replacing this setup with another that allows test cases to be co-located +// with/nested within the rules. This way any rule change only requires modifications in a single +// place, rather than in three. @BugPattern(summary = "Exercises a Refaster rule collection", linkType = NONE, severity = ERROR) @SuppressWarnings("java:S2160" /* Super class equality definition suffices. */) public final class RefasterRuleCollection extends BugChecker implements CompilationUnitTreeMatcher { @@ -319,7 +322,7 @@ public final class RefasterRuleCollection extends BugChecker implements Compilat return indexedMatches.subRangeMap(Range.closedOpen(startPosition, endPosition)); } - private ImmutableListMultimap getUnexpectedMatchesByLineNumber( + private static ImmutableListMultimap getUnexpectedMatchesByLineNumber( ImmutableRangeMap matches, String ruleUnderTest, VisitorState state) { LineMap lineMap = state.getPath().getCompilationUnit().getLineMap(); return matches.asMapOfRanges().entrySet().stream() diff --git a/website/_data/compatibility.yml b/website/_data/compatibility.yml index 3e2d10d4..9a244280 100644 --- a/website/_data/compatibility.yml +++ b/website/_data/compatibility.yml @@ -1,8 +1,101 @@ # An overview of Error Prone Support releases, along with compatible Error # Prone releases. This data was generated by `generate-version-compatibility-overview.sh`. releases: + - version: 0.19.1 + compatible: + - "2.35.1" + - "2.35.0" + - "2.34.0" + - "2.33.0" + - "2.32.0" + - "2.31.0" + - "2.30.0" + - version: 0.19.0 + compatible: + - "2.35.1" + - "2.35.0" + - "2.34.0" + - "2.33.0" + - "2.32.0" + - "2.31.0" + - "2.30.0" + - version: 0.18.0 + compatible: + - "2.35.1" + - "2.35.0" + - "2.34.0" + - "2.33.0" + - "2.32.0" + - "2.31.0" + - "2.30.0" + - version: 0.17.0 + compatible: + - "2.29.2" + - "2.29.1" + - "2.29.0" + - "2.28.0" + - "2.27.1" + - "2.27.0" + - "2.26.1" + - "2.26.0" + - "2.25.0" + - "2.24.1" + - "2.24.0" + - "2.23.0" + - version: 0.16.1 + compatible: + - "2.29.2" + - "2.29.1" + - "2.29.0" + - "2.28.0" + - "2.27.1" + - "2.27.0" + - "2.26.1" + - "2.26.0" + - "2.25.0" + - "2.24.1" + - "2.24.0" + - "2.23.0" + - version: 0.16.0 + compatible: + - "2.29.2" + - "2.29.1" + - "2.29.0" + - "2.28.0" + - "2.27.1" + - "2.27.0" + - "2.26.1" + - "2.26.0" + - "2.25.0" + - "2.24.1" + - "2.24.0" + - "2.23.0" + - version: 0.15.0 + compatible: + - "2.29.2" + - "2.29.1" + - "2.29.0" + - "2.28.0" + - "2.27.1" + - "2.27.0" + - "2.26.1" + - "2.26.0" + - "2.25.0" + - "2.24.1" + - "2.24.0" + - "2.23.0" - version: 0.14.0 compatible: + - "2.29.2" + - "2.29.1" + - "2.29.0" + - "2.28.0" + - "2.27.1" + - "2.27.0" + - "2.26.1" + - "2.26.0" + - "2.25.0" + - "2.24.1" - "2.24.0" - "2.23.0" - "2.22.0" diff --git a/website/generate-version-compatibility-overview.sh b/website/generate-version-compatibility-overview.sh index b4db15c5..7513a035 100755 --- a/website/generate-version-compatibility-overview.sh +++ b/website/generate-version-compatibility-overview.sh @@ -10,15 +10,21 @@ source "${HOME}/.sdkman/bin/sdkman-init.sh" set -e -u -o pipefail +# Currently all released Error Prone Support versions are compatible with Java +# 17. +java_version=17.0.13-tem +(set +u && echo n | sdk install java "${java_version}") +sdk use java "${java_version}" + output_file="$(dirname "${0}")/_data/compatibility.yml" -ep_versions=$( +ep_versions="$( git ls-remote \ --exit-code --refs --sort='-v:refname' \ https://github.com/google/error-prone.git \ 'v*.*' \ | grep -oP '(?<=/v)[^/]+$' -) +)" work_dir="$(mktemp -d)" trap 'rm -rf -- "${work_dir=}"' INT TERM HUP EXIT @@ -39,26 +45,43 @@ for eps_version in ${eps_versions}; do (set +u && echo n | sdk install maven "${mvn_version}") sdk use maven "${mvn_version}" + # Collect the list of checks supported by this version of Error Prone + # Support. + # XXX: Conditionally omit the `MethodReferenceUsage` exclusion once that + # check is production-ready. + mvn clean compile -Dverification.skip -DskipTests + checks="$( + find \ + -path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \ + -not -path "*/error-prone-experimental/*" \ + -not -path "*/error-prone-guidelines/*" \ + -print0 \ + | xargs -0 grep -hoP '[^.]+$' \ + | grep -v '^MethodReferenceUsage$' \ + | paste -s -d ',' - + )" + # Remove any Error Prone flags used by this build that may not be compatible # with the targeted version of Error Prone. Removal of these build flags does # not influence the compatibility assessment. sed -i -r 's,-XepAllSuggestionsAsWarnings|-Xep:\w+:\w+,,g' pom.xml # Using each Error Prone release, attempt to build and test the source, while - # also applying the Maven Central-hosted Refaster rules. This determines - # source and behavioral (in)compatibility with Error Prone APIs, while also - # assessing whether the Refaster rules are deserialization-compatible. + # also applying the Maven Central-hosted Error Prone Support-defined checks + # and Refaster rules. This determines source and behavioral (in)compatibility + # with Error Prone APIs, while also assessing whether the Refaster rules are + # deserialization-compatible. for ep_version in ${ep_versions}; do echo "Testing Error Prone Support ${eps_version} with Error Prone ${ep_version}..." mvn clean test \ -Perror-prone \ - -Derror-prone.patch-checks=Refaster \ + -Derror-prone.patch-checks="${checks}" \ -Ppatch \ -Pself-check \ -Dverification.skip \ -Dversion.error-prone-orig="${ep_version}" \ && echo "SUCCESS: { \"eps_version\": \"${eps_version}\", \"ep_version\": \"${ep_version}\" }" || true - # Undo any changes applied by Refaster. + # Undo any changes applied by the checks. git checkout -- '*.java' done done | tee "${build_log}"