mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
119 Commits
v0.18.0
...
rossendrij
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f9ae3263e | ||
|
|
bdef83bce5 | ||
|
|
f7f665681d | ||
|
|
7c0d544cf8 | ||
|
|
9390b6f571 | ||
|
|
176a833d89 | ||
|
|
3cacd27248 | ||
|
|
f06a2e4d43 | ||
|
|
cd06288f5b | ||
|
|
4b458e01bd | ||
|
|
eccfc34c78 | ||
|
|
0317cb73d6 | ||
|
|
ce6931cc36 | ||
|
|
9940576ea8 | ||
|
|
caf2a86922 | ||
|
|
507d759d02 | ||
|
|
8ec4936980 | ||
|
|
0f0b27abb7 | ||
|
|
2b1dbd98cd | ||
|
|
e0583a8f0a | ||
|
|
21437a43aa | ||
|
|
cf8af8c5cf | ||
|
|
07bd4b0b54 | ||
|
|
d1765fea0e | ||
|
|
5922c5b032 | ||
|
|
56b60f5cf6 | ||
|
|
be6b17b7dc | ||
|
|
f782ec2d8f | ||
|
|
6e88e3cea8 | ||
|
|
09fb21358a | ||
|
|
c27a626042 | ||
|
|
da33e800fa | ||
|
|
bc13976cc7 | ||
|
|
7f1f0656fd | ||
|
|
4e99382f42 | ||
|
|
9aa03aa842 | ||
|
|
71cc09a7e7 | ||
|
|
9b4cbfb84d | ||
|
|
f2238c779c | ||
|
|
6e893e9869 | ||
|
|
7ee8826e96 | ||
|
|
a0689f62b4 | ||
|
|
1b00c87b2f | ||
|
|
0bce1a0e29 | ||
|
|
ce18b0c058 | ||
|
|
4f2c143b9c | ||
|
|
f4740e0e64 | ||
|
|
3a155169ef | ||
|
|
13847f6d2c | ||
|
|
6a81b92e11 | ||
|
|
5794b404f2 | ||
|
|
b2d7ed4dc7 | ||
|
|
0fdc102aa5 | ||
|
|
ce3cf6a2d0 | ||
|
|
0f657f8835 | ||
|
|
5f56f43c40 | ||
|
|
ef6faf518a | ||
|
|
aa08d954a0 | ||
|
|
e4d1818ed0 | ||
|
|
beb96f0f4b | ||
|
|
4d1eeb2be1 | ||
|
|
9cbc6f875c | ||
|
|
5583630fb4 | ||
|
|
91e841ce12 | ||
|
|
3956a82cd5 | ||
|
|
c57debdc25 | ||
|
|
6a13efded8 | ||
|
|
432cf4560d | ||
|
|
966fb36ac9 | ||
|
|
c63d9350d2 | ||
|
|
e74874b04c | ||
|
|
f821d3775b | ||
|
|
3d5ee10d93 | ||
|
|
26da67d1f5 | ||
|
|
b3ca01a6c7 | ||
|
|
9f222e9efe | ||
|
|
097af51a3e | ||
|
|
c62e6c1127 | ||
|
|
5960423c4e | ||
|
|
188715c3c0 | ||
|
|
fb45bb00ed | ||
|
|
fd5fc913ce | ||
|
|
ae20c6069d | ||
|
|
8457bd5026 | ||
|
|
fcfb97b0e0 | ||
|
|
15680b4cb3 | ||
|
|
afebfbf478 | ||
|
|
fe2ac938f3 | ||
|
|
078d8c16fa | ||
|
|
c679a3fc0c | ||
|
|
de54b4bf64 | ||
|
|
ea60241782 | ||
|
|
e4f928addb | ||
|
|
059cc9e2db | ||
|
|
1e43c28c95 | ||
|
|
a07e9b3115 | ||
|
|
aec38d2e33 | ||
|
|
5b77663288 | ||
|
|
97c2bbd4b1 | ||
|
|
9c8fbfd36a | ||
|
|
38e6c3fdb5 | ||
|
|
5b1d82cfeb | ||
|
|
0821a95fcc | ||
|
|
f4afe457cb | ||
|
|
fd56ca8b6e | ||
|
|
882794d63b | ||
|
|
73ed6e32c7 | ||
|
|
ca5c3dd3b4 | ||
|
|
f6e9dbb996 | ||
|
|
260021c961 | ||
|
|
ec54e79f3e | ||
|
|
4cbfdba521 | ||
|
|
d94f65a1f0 | ||
|
|
9afdf2ddf3 | ||
|
|
060c901479 | ||
|
|
1feee4f64a | ||
|
|
552ddf6a7d | ||
|
|
5d92c6c6ce | ||
|
|
fa8ca80040 |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -42,9 +42,9 @@ Please replace this sentence with log output, if applicable.
|
||||
<!-- Please complete the following information: -->
|
||||
|
||||
- Operating system (e.g. MacOS Monterey).
|
||||
- Java version (i.e. `java --version`, e.g. `17.0.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
|
||||
|
||||
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -9,17 +9,17 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 17.0.10, 21.0.2, 22.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 }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
java-distribution: ${{ matrix.distribution }}
|
||||
maven-version: 3.9.6
|
||||
maven-version: 3.9.9
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
- name: Build project against vanilla Error Prone, compile Javadoc
|
||||
|
||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -19,7 +19,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
|
||||
with:
|
||||
|
||||
6
.github/workflows/deploy-website.yml
vendored
6
.github/workflows/deploy-website.yml
vendored
@@ -9,7 +9,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0
|
||||
- uses: ruby/setup-ruby@7d3497fd78c07c0d84ebafa58d8dac60cd1f0763 # v1.199.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
@@ -68,7 +68,7 @@ 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 }}
|
||||
|
||||
2
.github/workflows/openssf-scorecard.yml
vendored
2
.github/workflows/openssf-scorecard.yml
vendored
@@ -18,7 +18,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
|
||||
6
.github/workflows/pitest-analyze-pr.yml
vendored
6
.github/workflows/pitest-analyze-pr.yml
vendored
@@ -9,7 +9,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -25,9 +25,9 @@ jobs:
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.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
|
||||
|
||||
6
.github/workflows/pitest-update-pr.yml
vendored
6
.github/workflows/pitest-update-pr.yml
vendored
@@ -17,7 +17,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
- name: Check out code and set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.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@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
|
||||
with:
|
||||
|
||||
6
.github/workflows/run-integration-tests.yml
vendored
6
.github/workflows/run-integration-tests.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
name: On-demand integration test
|
||||
if: |
|
||||
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Install Harden-Runner
|
||||
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -36,9 +36,9 @@ jobs:
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
|
||||
with:
|
||||
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
|
||||
java-version: 17.0.10
|
||||
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
|
||||
|
||||
6
.github/workflows/sonarcloud.yml
vendored
6
.github/workflows/sonarcloud.yml
vendored
@@ -16,7 +16,7 @@ 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@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
|
||||
@@ -38,9 +38,9 @@ jobs:
|
||||
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
--batch-mode
|
||||
--errors
|
||||
--strict-checksums
|
||||
-Dstyle.color=always
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>documentation-support</artifactId>
|
||||
@@ -33,6 +33,15 @@
|
||||
<artifactId>error_prone_test_helpers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestCases;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from classes that test a {@code
|
||||
@@ -40,7 +40,7 @@ import tech.picnic.errorprone.documentation.BugPatternTestExtractor.TestCases;
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
public final class BugPatternTestExtractor implements Extractor<BugPatternTestCases> {
|
||||
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
|
||||
public BugPatternTestExtractor() {}
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
public Optional<BugPatternTestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
BugPatternTestCollector collector = new BugPatternTestCollector();
|
||||
|
||||
collector.scan(tree, state);
|
||||
@@ -59,7 +59,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
.filter(not(ImmutableList::isEmpty))
|
||||
.map(
|
||||
tests ->
|
||||
new AutoValue_BugPatternTestExtractor_TestCases(
|
||||
new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
|
||||
state.getPath().getCompilationUnit().getSourceFile().toUri(),
|
||||
ASTHelpers.getSymbol(tree).className(),
|
||||
tests));
|
||||
@@ -95,10 +95,10 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.namedAnyOf("addOutputLines", "expectUnchanged");
|
||||
|
||||
private final List<TestCase> collectedTestCases = new ArrayList<>();
|
||||
private final List<BugPatternTestCase> collectedBugPatternTestCases = new ArrayList<>();
|
||||
|
||||
private ImmutableList<TestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedTestCases);
|
||||
private ImmutableList<BugPatternTestCase> getCollectedTests() {
|
||||
return ImmutableList.copyOf(collectedBugPatternTestCases);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,14 +110,14 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
classUnderTest -> {
|
||||
List<TestEntry> entries = new ArrayList<>();
|
||||
if (isReplacementTest) {
|
||||
extractReplacementTestCases(node, entries, state);
|
||||
extractReplacementBugPatternTestCases(node, entries, state);
|
||||
} else {
|
||||
extractIdentificationTestCases(node, entries, state);
|
||||
extractIdentificationBugPatternTestCases(node, entries, state);
|
||||
}
|
||||
|
||||
if (!entries.isEmpty()) {
|
||||
collectedTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_TestCase(
|
||||
collectedBugPatternTestCases.add(
|
||||
new AutoValue_BugPatternTestExtractor_BugPatternTestCase(
|
||||
classUnderTest, ImmutableList.copyOf(entries).reverse()));
|
||||
}
|
||||
});
|
||||
@@ -140,7 +140,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
: Optional.empty();
|
||||
}
|
||||
|
||||
private static void extractIdentificationTestCases(
|
||||
private static void extractIdentificationBugPatternTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (IDENTIFICATION_SOURCE_LINES.matches(tree, state)) {
|
||||
String path = ASTHelpers.constValue(tree.getArguments().get(0), String.class);
|
||||
@@ -155,11 +155,11 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractIdentificationTestCases(methodInvocation, sink, state);
|
||||
extractIdentificationBugPatternTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractReplacementTestCases(
|
||||
private static void extractReplacementBugPatternTestCases(
|
||||
MethodInvocationTree tree, List<TestEntry> sink, VisitorState state) {
|
||||
if (REPLACEMENT_OUTPUT_SOURCE_LINES.matches(tree, state)) {
|
||||
/*
|
||||
@@ -185,7 +185,7 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
|
||||
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
|
||||
if (receiver instanceof MethodInvocationTree methodInvocation) {
|
||||
extractReplacementTestCases(methodInvocation, sink, state);
|
||||
extractReplacementBugPatternTestCases(methodInvocation, sink, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,24 +208,26 @@ public final class BugPatternTestExtractor implements Extractor<TestCases> {
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCases.class)
|
||||
abstract static class TestCases {
|
||||
static TestCases create(URI source, String testClass, ImmutableList<TestCase> testCases) {
|
||||
return new AutoValue_BugPatternTestExtractor_TestCases(source, testClass, testCases);
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCases.class)
|
||||
abstract static class BugPatternTestCases {
|
||||
static BugPatternTestCases create(
|
||||
URI source, String testClass, ImmutableList<BugPatternTestCase> testCases) {
|
||||
return new AutoValue_BugPatternTestExtractor_BugPatternTestCases(
|
||||
source, testClass, testCases);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String testClass();
|
||||
|
||||
abstract ImmutableList<TestCase> testCases();
|
||||
abstract ImmutableList<BugPatternTestCase> testCases();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCase.class)
|
||||
abstract static class TestCase {
|
||||
static TestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
|
||||
return new AutoValue_BugPatternTestExtractor_TestCase(classUnderTest, entries);
|
||||
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_BugPatternTestCase.class)
|
||||
abstract static class BugPatternTestCase {
|
||||
static BugPatternTestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
|
||||
return new AutoValue_BugPatternTestExtractor_BugPatternTestCase(classUnderTest, entries);
|
||||
}
|
||||
|
||||
abstract String classUnderTest();
|
||||
|
||||
@@ -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", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.annotations.FormatMethod;
|
||||
import com.google.errorprone.annotations.Immutable;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
* An {@link Extractor} that describes how to extract data from Refaster rule input and output test
|
||||
* classes.
|
||||
*/
|
||||
// XXX: Drop this extractor if/when the Refaster test framework is reimplemented such that tests can
|
||||
// be located alongside rules, rather than in two additional resource files as currently required by
|
||||
// `RefasterRuleCollection`.
|
||||
@Immutable
|
||||
@AutoService(Extractor.class)
|
||||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */)
|
||||
public final class RefasterRuleCollectionTestExtractor implements Extractor<RefasterTestCases> {
|
||||
private static final Matcher<ClassTree> IS_REFASTER_RULE_COLLECTION_TEST_CASE =
|
||||
isSubtypeOf("tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase");
|
||||
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
|
||||
private static final Pattern TEST_CLASS_FILE_NAME_PATTERN =
|
||||
Pattern.compile(".*(Input|Output)\\.java");
|
||||
private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("test(.*)");
|
||||
private static final String LINE_SEPARATOR = "\n";
|
||||
private static final Splitter LINE_SPLITTER = Splitter.on(LINE_SEPARATOR);
|
||||
|
||||
/** Instantiates a new {@link RefasterRuleCollectionTestExtractor} instance. */
|
||||
public RefasterRuleCollectionTestExtractor() {}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "refaster-rule-collection-test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<RefasterTestCases> tryExtract(ClassTree tree, VisitorState state) {
|
||||
if (!IS_REFASTER_RULE_COLLECTION_TEST_CASE.matches(tree, state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
URI sourceFile = state.getPath().getCompilationUnit().getSourceFile().toUri();
|
||||
return Optional.of(
|
||||
RefasterTestCases.create(
|
||||
sourceFile,
|
||||
getRuleCollectionName(tree),
|
||||
isInputFile(sourceFile),
|
||||
getRefasterTestCases(tree, state)));
|
||||
}
|
||||
|
||||
private static String getRuleCollectionName(ClassTree tree) {
|
||||
String className = tree.getSimpleName().toString();
|
||||
|
||||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
|
||||
// aspects of `RefasterRuleCollectionTestCase` subtypes.
|
||||
return tryExtractPatternGroup(className, TEST_CLASS_NAME_PATTERN)
|
||||
.orElseThrow(
|
||||
violation(
|
||||
"Refaster rule collection test class name '%s' does not match '%s'",
|
||||
className, TEST_CLASS_NAME_PATTERN));
|
||||
}
|
||||
|
||||
private static boolean isInputFile(URI sourceFile) {
|
||||
String path = sourceFile.getPath();
|
||||
|
||||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key
|
||||
// aspects of `RefasterRuleCollectionTestCase` subtypes.
|
||||
return "Input"
|
||||
.equals(
|
||||
tryExtractPatternGroup(path, TEST_CLASS_FILE_NAME_PATTERN)
|
||||
.orElseThrow(
|
||||
violation(
|
||||
"Refaster rule collection test file name '%s' does not match '%s'",
|
||||
path, TEST_CLASS_FILE_NAME_PATTERN)));
|
||||
}
|
||||
|
||||
private static ImmutableList<RefasterTestCase> getRefasterTestCases(
|
||||
ClassTree tree, VisitorState state) {
|
||||
return tree.getMembers().stream()
|
||||
.filter(MethodTree.class::isInstance)
|
||||
.map(MethodTree.class::cast)
|
||||
.flatMap(m -> tryExtractRefasterTestCase(m, state).stream())
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static Optional<RefasterTestCase> tryExtractRefasterTestCase(
|
||||
MethodTree method, VisitorState state) {
|
||||
return tryExtractPatternGroup(method.getName().toString(), TEST_METHOD_NAME_PATTERN)
|
||||
.map(name -> RefasterTestCase.create(name, getFormattedSource(method, state)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source code for the specified method.
|
||||
*
|
||||
* @implNote This operation attempts to trim leading whitespace, such that the start and end of
|
||||
* the method declaration are aligned. The implemented heuristic assumes that the code is
|
||||
* formatted using Google Java Format.
|
||||
*/
|
||||
// XXX: Leading Javadoc and other comments are currently not extracted. Consider fixing this.
|
||||
private static String getFormattedSource(MethodTree method, VisitorState state) {
|
||||
String source = SourceCode.treeToString(method, state);
|
||||
int finalNewline = source.lastIndexOf(LINE_SEPARATOR);
|
||||
if (finalNewline < 0) {
|
||||
return source;
|
||||
}
|
||||
|
||||
int indentation = Math.max(0, source.lastIndexOf(' ') - finalNewline);
|
||||
String prefixToStrip = " ".repeat(indentation);
|
||||
|
||||
return LINE_SPLITTER
|
||||
.splitToStream(source)
|
||||
.map(line -> line.startsWith(prefixToStrip) ? line.substring(indentation) : line)
|
||||
.collect(joining(LINE_SEPARATOR));
|
||||
}
|
||||
|
||||
private static Optional<String> tryExtractPatternGroup(String input, Pattern pattern) {
|
||||
java.util.regex.Matcher matcher = pattern.matcher(input);
|
||||
return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty();
|
||||
}
|
||||
|
||||
@FormatMethod
|
||||
private static Supplier<VerifyException> violation(String format, Object... args) {
|
||||
return () -> new VerifyException(String.format(format, args));
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases.class)
|
||||
abstract static class RefasterTestCases {
|
||||
static RefasterTestCases create(
|
||||
URI source,
|
||||
String ruleCollection,
|
||||
boolean isInput,
|
||||
ImmutableList<RefasterTestCase> testCases) {
|
||||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases(
|
||||
source, ruleCollection, isInput, testCases);
|
||||
}
|
||||
|
||||
abstract URI source();
|
||||
|
||||
abstract String ruleCollection();
|
||||
|
||||
abstract boolean isInput();
|
||||
|
||||
abstract ImmutableList<RefasterTestCase> testCases();
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase.class)
|
||||
abstract static class RefasterTestCase {
|
||||
static RefasterTestCase create(String name, String content) {
|
||||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase(name, content);
|
||||
}
|
||||
|
||||
abstract String name();
|
||||
|
||||
abstract String content();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCase;
|
||||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases;
|
||||
|
||||
final class RefasterRuleCollectionTestExtractorTest {
|
||||
@Test
|
||||
void noRefasterRuleTest(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory, "NoRefasterRuleTest.java", "public final class NoRefasterRuleTest {}");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidTestClassName(@TempDir Path outputDirectory) {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"InvalidTestClassNameInput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class InvalidTestClassName implements RefasterRuleCollectionTestCase {}"))
|
||||
.cause()
|
||||
.isInstanceOf(VerifyException.class)
|
||||
.hasMessage(
|
||||
"Refaster rule collection test class name 'InvalidTestClassName' does not match '(.*)Test'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidFileName(@TempDir Path outputDirectory) {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"InvalidFileNameTest.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class InvalidFileNameTest implements RefasterRuleCollectionTestCase {}"))
|
||||
.cause()
|
||||
.isInstanceOf(VerifyException.class)
|
||||
.hasMessage(
|
||||
"Refaster rule collection test file name '/InvalidFileNameTest.java' does not match '.*(Input|Output)\\.java'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyRefasterRuleCollectionTestInput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"EmptyRefasterRuleCollectionTestInput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class EmptyRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"EmptyRefasterRuleCollectionTestInput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///EmptyRefasterRuleCollectionTestInput.java"),
|
||||
"EmptyRefasterRuleCollection",
|
||||
/* isInput= */ true,
|
||||
ImmutableList.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singletonRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"SingletonRefasterRuleCollectionTestOutput.java",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class SingletonRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
|
||||
" int testMyRule() {",
|
||||
" return 42;",
|
||||
" }",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"SingletonRefasterRuleCollectionTestOutput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///SingletonRefasterRuleCollectionTestOutput.java"),
|
||||
"SingletonRefasterRuleCollection",
|
||||
/* isInput= */ false,
|
||||
ImmutableList.of(
|
||||
RefasterTestCase.create(
|
||||
"MyRule",
|
||||
"""
|
||||
int testMyRule() {
|
||||
return 42;
|
||||
}"""))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void complexRefasterRuleCollectionTestOutput(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithDocumentationGenerator(
|
||||
outputDirectory,
|
||||
"pkg/ComplexRefasterRuleCollectionTestInput.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import com.google.common.collect.ImmutableSet;",
|
||||
"import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;",
|
||||
"",
|
||||
"final class ComplexRefasterRuleCollectionTest implements RefasterRuleCollectionTestCase {",
|
||||
" private static final String IGNORED_CONSTANT = \"constant\";",
|
||||
"",
|
||||
" @Override",
|
||||
" public ImmutableSet<Object> elidedTypesAndStaticImports() {",
|
||||
" return ImmutableSet.of();",
|
||||
" }",
|
||||
"",
|
||||
" /** Javadoc. */",
|
||||
" String testFirstRule() {",
|
||||
" return \"Don't panic\";",
|
||||
" }",
|
||||
"",
|
||||
" // Comment.",
|
||||
" String testSecondRule() {",
|
||||
" return \"Carry a towel\";",
|
||||
" }",
|
||||
"",
|
||||
" void testEmptyRule() {}",
|
||||
"}");
|
||||
|
||||
verifyGeneratedFileContent(
|
||||
outputDirectory,
|
||||
"ComplexRefasterRuleCollectionTestInput",
|
||||
RefasterTestCases.create(
|
||||
URI.create("file:///pkg/ComplexRefasterRuleCollectionTestInput.java"),
|
||||
"ComplexRefasterRuleCollection",
|
||||
/* isInput= */ true,
|
||||
ImmutableList.of(
|
||||
RefasterTestCase.create(
|
||||
"FirstRule",
|
||||
"""
|
||||
String testFirstRule() {
|
||||
return "Don't panic";
|
||||
}"""),
|
||||
RefasterTestCase.create(
|
||||
"SecondRule",
|
||||
"""
|
||||
String testSecondRule() {
|
||||
return "Carry a towel";
|
||||
}"""),
|
||||
RefasterTestCase.create("EmptyRule", "void testEmptyRule() {}"))));
|
||||
}
|
||||
|
||||
private static void verifyGeneratedFileContent(
|
||||
Path outputDirectory, String testIdentifier, RefasterTestCases expected) {
|
||||
assertThat(
|
||||
outputDirectory.resolve(
|
||||
String.format("refaster-rule-collection-test-%s.json", testIdentifier)))
|
||||
.exists()
|
||||
.returns(expected, path -> Json.read(path, RefasterTestCases.class));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -55,7 +55,12 @@
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<scope>provided</scope>
|
||||
<!-- XXX: One would expect this to be a `provided` dependency (as
|
||||
Refaster rules are interpreted by the `refaster-runner` module),
|
||||
but the `OptionalOrElseGet` bug checker defined by this module
|
||||
depends on the `RequiresComputation` matcher that
|
||||
`refaster-support` primarily exposes for use by Refaster rules.
|
||||
Review this setup. (Should the matchers be moved elsewhere?) -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
@@ -67,6 +72,11 @@
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
@@ -82,6 +92,11 @@
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
@@ -196,7 +211,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-11</artifactId>
|
||||
<artifactId>rewrite-java-17</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -287,6 +302,55 @@
|
||||
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<!-- The Refaster input/output test classes used by
|
||||
`RefasterRuleCollection` are modelled as classpath
|
||||
resources, and thus not subject to the default test
|
||||
compilation step. These two custom compilation steps
|
||||
serve two purposes:
|
||||
- To provide early feedback in case of syntax errors.
|
||||
- To enable the `DocumentationGenerator` compiler
|
||||
plugin to extract documentation metadata from them.
|
||||
Note that the input and output files must be compiled
|
||||
separately and to distinct output directories, as they
|
||||
define the same set of class names. -->
|
||||
<!-- XXX: Drop these executions if/when the Refaster
|
||||
test framework is reimplemented such that tests can be
|
||||
located alongside rules, rather than in two additional
|
||||
resource files. -->
|
||||
<execution>
|
||||
<id>compile-refaster-test-input</id>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<phase>process-test-resources</phase>
|
||||
<configuration>
|
||||
<compileSourceRoots>
|
||||
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
|
||||
</compileSourceRoots>
|
||||
<testIncludes>
|
||||
<testInclude>**/*Input.java</testInclude>
|
||||
</testIncludes>
|
||||
<outputDirectory>${project.build.directory}/refaster-test-input</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>compile-refaster-test-output</id>
|
||||
<goals>
|
||||
<goal>testCompile</goal>
|
||||
</goals>
|
||||
<phase>process-test-resources</phase>
|
||||
<configuration>
|
||||
<compileSourceRoots>
|
||||
<compileSourceRoot>${project.basedir}/src/test/resources</compileSourceRoot>
|
||||
</compileSourceRoots>
|
||||
<testIncludes>
|
||||
<testInclude>**/*Output.java</testInclude>
|
||||
</testIncludes>
|
||||
<outputDirectory>${project.build.directory}/refaster-test-output</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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<VariableTree> IS_CONSTANT =
|
||||
allOf(hasModifier(Modifier.STATIC), hasModifier(Modifier.FINAL));
|
||||
private static final Matcher<VariableTree> IS_PRIVATE = hasModifier(Modifier.PRIVATE);
|
||||
private static final Pattern SNAKE_CASE = Pattern.compile("([a-z])([A-Z])");
|
||||
private static final ImmutableSet<String> 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<String> 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<Boolean, @Nullable Void>() {
|
||||
@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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -51,6 +51,7 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
// is effectively the identity operation.
|
||||
// XXX: Also flag nullary instance method invocations that represent an identity conversion, such as
|
||||
// `Boolean#booleanValue()`, `Byte#byteValue()` and friends.
|
||||
// XXX: Also flag redundant round-trip conversions such as `path.toFile().toPath()`.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Avoid or clarify identity conversions",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -225,10 +229,8 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
excludedAnnotations(flags));
|
||||
}
|
||||
|
||||
private static ImmutableList<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
Set<String> exclusions = new HashSet<>();
|
||||
exclusions.addAll(Flags.getList(flags, EXCLUDED_ANNOTATIONS_FLAG));
|
||||
exclusions.addAll(BLACKLISTED_ANNOTATIONS);
|
||||
return ImmutableList.copyOf(exclusions);
|
||||
private static ImmutableSet<String> excludedAnnotations(ErrorProneFlags flags) {
|
||||
return Sets.union(BLACKLISTED_ANNOTATIONS, Flags.getSet(flags, EXCLUDED_ANNOTATIONS_FLAG))
|
||||
.immutableCopy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,12 @@ import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
|
||||
import tech.picnic.errorprone.utils.SourceCode;
|
||||
|
||||
/**
|
||||
@@ -36,12 +34,12 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
* it does, the suggested fix changes the program's semantics. Such fragile code must instead be
|
||||
* refactored such that the side-effectful code does not appear accidental.
|
||||
*/
|
||||
// XXX: Consider also implementing the inverse, in which `.orElseGet(() -> someConstant)` is
|
||||
// flagged.
|
||||
// XXX: Once the `MethodReferenceUsageCheck` becomes generally usable, consider leaving the method
|
||||
// reference cleanup to that check, and express the remainder of the logic in this class using a
|
||||
// Refaster template, i.c.w. a `@Matches` constraint that implements the `requiresComputation`
|
||||
// logic.
|
||||
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Review whether a `@Matcher` can be used to avoid this.
|
||||
// XXX: Once the `MethodReferenceUsageCheck` bug checker becomes generally usable, consider leaving
|
||||
// the method reference cleanup to that check, and express the remainder of the logic in this class
|
||||
// using a Refaster template, i.c.w. a `@NotMatches(RequiresComputation.class)` constraint.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
@@ -51,16 +49,17 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
linkType = NONE,
|
||||
severity = WARNING,
|
||||
tags = PERFORMANCE)
|
||||
public final class OptionalOrElse extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
public final class OptionalOrElseGet extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> REQUIRES_COMPUTATION = new RequiresComputation();
|
||||
private static final Matcher<ExpressionTree> OPTIONAL_OR_ELSE_METHOD =
|
||||
instanceMethod().onExactClass(Optional.class.getCanonicalName()).namedAnyOf("orElse");
|
||||
// XXX: Also exclude invocations of `@Placeholder`-annotated methods.
|
||||
private static final Matcher<ExpressionTree> REFASTER_METHOD =
|
||||
staticMethod().onClass(Refaster.class.getCanonicalName());
|
||||
|
||||
/** Instantiates a new {@link OptionalOrElse} instance. */
|
||||
public OptionalOrElse() {}
|
||||
/** Instantiates a new {@link OptionalOrElseGet} instance. */
|
||||
public OptionalOrElseGet() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
@@ -69,7 +68,8 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
}
|
||||
|
||||
ExpressionTree argument = Iterables.getOnlyElement(tree.getArguments());
|
||||
if (!requiresComputation(argument) || REFASTER_METHOD.matches(argument, state)) {
|
||||
if (!REQUIRES_COMPUTATION.matches(argument, state)
|
||||
|| REFASTER_METHOD.matches(argument, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@@ -91,18 +91,6 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
return describeMatch(tree, fix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether the given expression contains anything other than a literal or a (possibly
|
||||
* dereferenced) variable or constant.
|
||||
*/
|
||||
private static boolean requiresComputation(ExpressionTree tree) {
|
||||
return !(tree instanceof IdentifierTree
|
||||
|| tree instanceof LiteralTree
|
||||
|| (tree instanceof MemberSelectTree memberSelect
|
||||
&& !requiresComputation(memberSelect.getExpression()))
|
||||
|| ASTHelpers.constValue(tree) != null);
|
||||
}
|
||||
|
||||
/** Returns the nullary method reference matching the given expression, if any. */
|
||||
private static Optional<String> tryMethodReferenceConversion(
|
||||
ExpressionTree tree, VisitorState state) {
|
||||
@@ -118,7 +106,7 @@ public final class OptionalOrElse extends BugChecker implements MethodInvocation
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (requiresComputation(memberSelect.getExpression())) {
|
||||
if (REQUIRES_COMPUTATION.matches(memberSelect.getExpression(), state)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -378,11 +378,11 @@ public final class RedundantStringConversion extends BugChecker
|
||||
|
||||
private static Matcher<MethodInvocationTree> 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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Tree> isSubtypeOfAny(ImmutableList<String> inclusions) {
|
||||
private static Matcher<Tree> isSubtypeOfAny(ImmutableSet<String> inclusions) {
|
||||
return anyOf(
|
||||
inclusions.stream()
|
||||
.map(inclusion -> isSubtypeOf(Suppliers.typeFromString(inclusion)))
|
||||
|
||||
@@ -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<ExpressionTree> 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<ExpressionTree> IS_STATIC_ENCLOSING_CLASS_REFERENCE =
|
||||
classLiteral(Slf4jLoggerDeclaration::isEnclosingClassReference);
|
||||
private static final Matcher<ExpressionTree> IS_DYNAMIC_ENCLOSING_CLASS_REFERENCE =
|
||||
toType(
|
||||
MethodInvocationTree.class,
|
||||
allOf(
|
||||
instanceMethod().anyClass().named("getClass").withNoParameters(),
|
||||
Slf4jLoggerDeclaration::getClassReceiverIsEnclosingClassInstance));
|
||||
private static final ImmutableSet<Modifier> INSTANCE_DECLARATION_MODIFIERS =
|
||||
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.FINAL);
|
||||
private static final ImmutableSet<Modifier> 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<Modifier> 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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Stream<T> stream) {
|
||||
return stream.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
ImmutableSet<T> after(Stream<T> stream) {
|
||||
return stream.collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Iterators#getNext(Iterator, Object)} over more contrived alternatives. */
|
||||
static final class IteratorGetNextOrDefault<T> {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,24 +73,6 @@ final class ClassRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Class#cast(Object)} method references over lambda expressions that require naming
|
||||
* a variable.
|
||||
*/
|
||||
// XXX: Once the `ClassReferenceCast` rule is dropped, rename this rule to just `ClassCast`.
|
||||
static final class ClassLiteralCast<T, S> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<T, S> before() {
|
||||
return t -> (S) t;
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Function<T, S> after() {
|
||||
return Refaster.<S>clazz()::cast;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Class#cast(Object)} method references over lambda expressions that require naming
|
||||
* a variable.
|
||||
|
||||
@@ -365,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<T> {
|
||||
/** Prefer {@link Collection#iterator()} over more contrived or less efficient alternatives. */
|
||||
static final class CollectionIterator<T> {
|
||||
@BeforeTemplate
|
||||
Iterator<T> before(Collection<T> collection) {
|
||||
return collection.stream().iterator();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
Iterator<T> before(ImmutableCollection<T> collection) {
|
||||
return collection.asList().iterator();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterator<T> after(ImmutableCollection<T> collection) {
|
||||
Iterator<T> after(Collection<T> collection) {
|
||||
return collection.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
@@ -243,18 +245,77 @@ final class ComparatorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#sort(List)} over more verbose alternatives. */
|
||||
static final class CollectionsSort<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
void before(List<T> collection) {
|
||||
Collections.sort(collection, naturalOrder());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
void after(List<T> collection) {
|
||||
Collections.sort(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#min(Collection)} over more verbose alternatives. */
|
||||
static final class CollectionsMin<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection) {
|
||||
return Refaster.anyOf(
|
||||
Collections.min(collection, naturalOrder()), Collections.max(collection, reverseOrder()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection) {
|
||||
return Collections.min(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MinOfVarargs<T> {
|
||||
static final class MinOfArray<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<T> cmp) {
|
||||
T before(T[] array, Comparator<S> cmp) {
|
||||
return Arrays.stream(array).min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(T[] array, Comparator<S> cmp) {
|
||||
return Collections.min(Arrays.asList(array), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class CollectionsMinWithComparator<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection, Comparator<S> cmp) {
|
||||
return collection.stream().min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection, Comparator<S> cmp) {
|
||||
return Collections.min(collection, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MinOfVarargs<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<S> cmp) {
|
||||
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(@Repeated T value, Comparator<T> cmp) {
|
||||
T after(@Repeated T value, Comparator<S> cmp) {
|
||||
return Collections.min(Arrays.asList(value), cmp);
|
||||
}
|
||||
}
|
||||
@@ -310,18 +371,64 @@ final class ComparatorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Collections#max(Collection)} over more verbose alternatives. */
|
||||
static final class CollectionsMax<T extends Comparable<? super T>> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection) {
|
||||
return Refaster.anyOf(
|
||||
Collections.max(collection, naturalOrder()), Collections.min(collection, reverseOrder()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection) {
|
||||
return Collections.max(collection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MaxOfVarargs<T> {
|
||||
static final class MaxOfArray<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<T> cmp) {
|
||||
T before(T[] array, Comparator<S> cmp) {
|
||||
return Arrays.stream(array).max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(T[] array, Comparator<S> cmp) {
|
||||
return Collections.max(Arrays.asList(array), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class CollectionsMaxWithComparator<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(Collection<T> collection, Comparator<S> cmp) {
|
||||
return collection.stream().max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Collection<T> collection, Comparator<S> cmp) {
|
||||
return Collections.max(collection, cmp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
|
||||
* of values.
|
||||
*/
|
||||
static final class MaxOfVarargs<S, T extends S> {
|
||||
@BeforeTemplate
|
||||
T before(@Repeated T value, Comparator<S> cmp) {
|
||||
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(@Repeated T value, Comparator<T> cmp) {
|
||||
T after(@Repeated T value, Comparator<S> cmp) {
|
||||
return Collections.max(Arrays.asList(value), cmp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Repeated;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
@@ -18,6 +21,49 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
final class FileRules {
|
||||
private FileRules() {}
|
||||
|
||||
/** Prefer the more idiomatic {@link Path#of(URI)} over {@link Paths#get(URI)}. */
|
||||
static final class PathOfUri {
|
||||
@BeforeTemplate
|
||||
Path before(URI uri) {
|
||||
return Paths.get(uri);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(URI uri) {
|
||||
return Path.of(uri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer the more idiomatic {@link Path#of(String, String...)} over {@link Paths#get(String,
|
||||
* String...)}.
|
||||
*/
|
||||
static final class PathOfString {
|
||||
@BeforeTemplate
|
||||
Path before(String first, @Repeated String more) {
|
||||
return Paths.get(first, more);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(String first, @Repeated String more) {
|
||||
return Path.of(first, more);
|
||||
}
|
||||
}
|
||||
|
||||
/** Avoid redundant conversions from {@link Path} to {@link File}. */
|
||||
// XXX: Review whether a rule such as this one is better handled by the `IdentityConversion` rule.
|
||||
static final class PathInstance {
|
||||
@BeforeTemplate
|
||||
Path before(Path path) {
|
||||
return path.toFile().toPath();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Path after(Path path) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Files#readString(Path, Charset)} over more contrived alternatives. */
|
||||
static final class FilesReadStringWithCharset {
|
||||
@BeforeTemplate
|
||||
@@ -50,7 +96,11 @@ final class FileRules {
|
||||
*/
|
||||
static final class FilesCreateTempFileToFile {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("java:S5443" /* This violation will be rewritten. */)
|
||||
@SuppressWarnings({
|
||||
"FilesCreateTempFileInCustomDirectoryToFile" /* This is a more specific template. */,
|
||||
"java:S5443" /* This violation will be rewritten. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
File before(String prefix, String suffix) throws IOException {
|
||||
return Refaster.anyOf(
|
||||
File.createTempFile(prefix, suffix), File.createTempFile(prefix, suffix, null));
|
||||
@@ -63,4 +113,20 @@ final class FileRules {
|
||||
return Files.createTempFile(prefix, suffix).toFile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Files#createTempFile(Path, String, String, FileAttribute[])} over alternatives
|
||||
* that create files with more liberal permissions.
|
||||
*/
|
||||
static final class FilesCreateTempFileInCustomDirectoryToFile {
|
||||
@BeforeTemplate
|
||||
File before(File directory, String prefix, String suffix) throws IOException {
|
||||
return File.createTempFile(prefix, suffix, directory);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
File after(File directory, String prefix, String suffix) throws IOException {
|
||||
return Files.createTempFile(directory.toPath(), prefix, suffix).toFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Iterable<T> elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Collection<T> elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> after(Iterable<T> elements) {
|
||||
return Sets.immutableEnumSet(elements);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Sets#immutableEnumSet(Iterable)} for enum collections to take advantage of the
|
||||
* internally used {@link EnumSet}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(T[] elements) {
|
||||
return ImmutableSet.copyOf(elements);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1) {
|
||||
return Refaster.anyOf(ImmutableSet.of(e1), ImmutableSet.copyOf(EnumSet.of(e1)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> 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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, T e2) {
|
||||
return Refaster.anyOf(ImmutableSet.of(e1, e2), ImmutableSet.copyOf(EnumSet.of(e1, e2)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<T> 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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> 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<T> 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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> 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<T> 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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> 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<T> 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}.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> 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<T> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("SetsImmutableEnumSetIterable" /* This is a more specific template. */)
|
||||
ImmutableSet<T> before(T e1, @Repeated T elements) {
|
||||
return ImmutableSet.copyOf(EnumSet.of(e1, Refaster.asVarargs(elements)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ImmutableSet<T> 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.
|
||||
*
|
||||
* <p><strong>Warning:</strong> 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<T extends Enum<T>> {
|
||||
@BeforeTemplate
|
||||
ImmutableSet<T> before(Stream<T> stream) {
|
||||
return stream.collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
ImmutableSet<T> after(Stream<T> stream) {
|
||||
return stream.collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Tag> before(Tag tag) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag), ImmutableList.of(tag));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag) {
|
||||
return Tags.of(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf2 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag1, tag2), ImmutableList.of(tag1, tag2));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> after(Tag tag1, Tag tag2) {
|
||||
return Tags.of(tag1, tag2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer using {@link Tags} over other immutable collections. */
|
||||
static final class TagsOf3 {
|
||||
@BeforeTemplate
|
||||
ImmutableCollection<Tag> before(Tag tag1, Tag tag2, Tag tag3) {
|
||||
return Refaster.anyOf(ImmutableSet.of(tag1, tag2, tag3), ImmutableList.of(tag1, tag2, tag3));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Iterable<Tag> 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<Tag> 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<Tag> 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<Tag> 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<Tag> after(Tag tag1, Tag tag2, Tag tag3, Tag tag4, Tag tag5) {
|
||||
return Tags.of(tag1, tag2, tag3, tag4, tag5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;
|
||||
import tech.picnic.errorprone.refaster.matchers.RequiresComputation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link Optional}s. */
|
||||
@OnlineDocumentation
|
||||
@@ -255,24 +255,21 @@ final class OptionalRules {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Optional#orElseGet(Supplier)} over {@link Optional#orElse(Object)} if the
|
||||
* fallback value is not the result of a trivial computation.
|
||||
* Prefer {@link Optional#orElse(Object)} over {@link Optional#orElseGet(Supplier)} if the
|
||||
* fallback value does not require non-trivial computation.
|
||||
*/
|
||||
// XXX: This rule may introduce a compilation error: the `value` expression may reference a
|
||||
// non-effectively final variable, which is not allowed in the replacement lambda expression.
|
||||
// Review whether a `@Matcher` can be used to avoid this.
|
||||
// XXX: Once `MethodReferenceUsage` is "production ready", replace
|
||||
// `@NotMatches(IsLikelyTrivialComputation.class)` with `@Matches(RequiresComputation.class)` (and
|
||||
// reimplement the matcher accordingly).
|
||||
static final class OptionalOrElseGet<T> {
|
||||
// XXX: This rule is the counterpart to the `OptionalOrElseGet` bug checker. Once the
|
||||
// `MethodReferenceUsage` bug checker is "production ready", that bug checker may similarly be
|
||||
// replaced with a Refaster rule.
|
||||
static final class OptionalOrElse<T> {
|
||||
@BeforeTemplate
|
||||
T before(Optional<T> optional, @NotMatches(IsLikelyTrivialComputation.class) T value) {
|
||||
return optional.orElse(value);
|
||||
T before(Optional<T> optional, @NotMatches(RequiresComputation.class) T value) {
|
||||
return optional.orElseGet(() -> value);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
T after(Optional<T> optional, T value) {
|
||||
return optional.orElseGet(() -> value);
|
||||
return optional.orElse(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +370,12 @@ final class OptionalRules {
|
||||
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
|
||||
static final class OptionalOrOtherOptional<T> {
|
||||
@BeforeTemplate
|
||||
@SuppressWarnings("NestedOptionals")
|
||||
@SuppressWarnings({
|
||||
"LexicographicalAnnotationAttributeListing" /* `key-*` entry must remain last. */,
|
||||
"NestedOptionals" /* This violation will be rewritten. */,
|
||||
"OptionalOrElse" /* Parameters represent expressions that may require computation. */,
|
||||
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
|
||||
})
|
||||
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
|
||||
// XXX: Note that rewriting the first and third variant will change the code's behavior if
|
||||
// `optional2` has side-effects.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -1956,6 +1957,22 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't propagate {@link Mono} cancellations to an upstream cache value computation, as
|
||||
* completion of such computations may benefit concurrent or subsequent cache usages.
|
||||
*/
|
||||
static final class MonoFromFutureAsyncLoadingCacheGet<K, V> {
|
||||
@BeforeTemplate
|
||||
Mono<V> before(AsyncLoadingCache<K, V> cache, K key) {
|
||||
return Mono.fromFuture(() -> cache.get(key));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<V> after(AsyncLoadingCache<K, V> cache, K key) {
|
||||
return Mono.fromFuture(() -> cache.get(key), /* suppressCancel= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former
|
||||
* yields a {@link Flux} that is more likely to behave as expected when subscribed to more than
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.<ImmutableSet>of(ImmutableSet.of(5)).map(s -> (ImmutableSet<Number>) 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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();",
|
||||
|
||||
@@ -5,14 +5,15 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class OptionalOrElseTest {
|
||||
final class OptionalOrElseGetTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(OptionalOrElse.class, getClass())
|
||||
CompilationTestHelper.newInstance(OptionalOrElseGet.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.refaster.Refaster;",
|
||||
"import java.util.Optional;",
|
||||
"import java.util.function.Supplier;",
|
||||
"",
|
||||
"class A {",
|
||||
" private final Optional<Object> optional = Optional.empty();",
|
||||
@@ -27,6 +28,7 @@ final class OptionalOrElseTest {
|
||||
" optional.orElse(string);",
|
||||
" optional.orElse(this.string);",
|
||||
" optional.orElse(Refaster.anyOf(\"constant\", \"another\"));",
|
||||
" Optional.<Supplier<String>>empty().orElse(() -> \"constant\");",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Optional.empty().orElse(string + \"constant\");",
|
||||
@@ -67,7 +69,7 @@ final class OptionalOrElseTest {
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElse.class, getClass())
|
||||
BugCheckerRefactoringTestHelper.newInstance(OptionalOrElseGet.class, getClass())
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.Optional;",
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIteratorGetNextOrDefault() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableList.of("a").iterator().hasNext()
|
||||
|
||||
@@ -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<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIteratorGetNextOrDefault() {
|
||||
return ImmutableSet.of(
|
||||
Iterators.getNext(ImmutableList.of("a").iterator(), "foo"),
|
||||
|
||||
@@ -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<Boolean> testNameContentEquals() {
|
||||
return ImmutableSet.of(
|
||||
((Name) null).toString().equals("foo".subSequence(0, 1).toString()),
|
||||
((com.sun.tools.javac.util.Name) null).toString().equals("bar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Boolean> testNameContentEquals() {
|
||||
return ImmutableSet.of(
|
||||
((Name) null).contentEquals("foo".subSequence(0, 1)),
|
||||
((com.sun.tools.javac.util.Name) null).contentEquals("bar"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,6 @@ final class ClassRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return s -> clazz.isInstance(s);
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassLiteralCast() {
|
||||
return i -> (Integer) i;
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassReferenceCast() {
|
||||
return i -> Integer.class.cast(i);
|
||||
}
|
||||
|
||||
@@ -24,10 +24,6 @@ final class ClassRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return clazz::isInstance;
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassLiteralCast() {
|
||||
return Integer.class::cast;
|
||||
}
|
||||
|
||||
Function<Number, Integer> testClassReferenceCast() {
|
||||
return Integer.class::cast;
|
||||
}
|
||||
|
||||
@@ -119,8 +119,9 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).asList().toArray(Integer[]::new);
|
||||
}
|
||||
|
||||
Iterator<Integer> testImmutableCollectionIterator() {
|
||||
return ImmutableSet.of(1).asList().iterator();
|
||||
ImmutableSet<Iterator<Integer>> testCollectionIterator() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(1).stream().iterator(), ImmutableSet.of(2).asList().iterator());
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<Integer>> testOptionalFirstCollectionElement() {
|
||||
|
||||
@@ -109,8 +109,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(1).toArray(Integer[]::new);
|
||||
}
|
||||
|
||||
Iterator<Integer> testImmutableCollectionIterator() {
|
||||
return ImmutableSet.of(1).iterator();
|
||||
ImmutableSet<Iterator<Integer>> testCollectionIterator() {
|
||||
return ImmutableSet.of(ImmutableSet.of(1).iterator(), ImmutableSet.of(2).iterator());
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<Integer>> testOptionalFirstCollectionElement() {
|
||||
|
||||
@@ -107,6 +107,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Comparator.<String>reverseOrder().compare("baz", "qux"));
|
||||
}
|
||||
|
||||
void testCollectionsSort() {
|
||||
Collections.sort(ImmutableList.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMin() {
|
||||
return ImmutableSet.of(
|
||||
Collections.min(ImmutableList.of("foo"), naturalOrder()),
|
||||
Collections.max(ImmutableList.of("bar"), reverseOrder()));
|
||||
}
|
||||
|
||||
String testMinOfArray() {
|
||||
return Arrays.stream(new String[0]).min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
String testCollectionsMinWithComparator() {
|
||||
return ImmutableSet.of("foo", "bar").stream().min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
int testMinOfVarargs() {
|
||||
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
|
||||
}
|
||||
@@ -135,6 +153,20 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMax() {
|
||||
return ImmutableSet.of(
|
||||
Collections.max(ImmutableList.of("foo"), naturalOrder()),
|
||||
Collections.min(ImmutableList.of("bar"), reverseOrder()));
|
||||
}
|
||||
|
||||
String testMaxOfArray() {
|
||||
return Arrays.stream(new String[0]).max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
String testCollectionsMaxWithComparator() {
|
||||
return ImmutableSet.of("foo", "bar").stream().max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
int testMaxOfVarargs() {
|
||||
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,23 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
|
||||
}
|
||||
|
||||
void testCollectionsSort() {
|
||||
Collections.sort(ImmutableList.of("foo", "bar"));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMin() {
|
||||
return ImmutableSet.of(
|
||||
Collections.min(ImmutableList.of("foo")), Collections.min(ImmutableList.of("bar")));
|
||||
}
|
||||
|
||||
String testMinOfArray() {
|
||||
return Collections.min(Arrays.asList(new String[0]), naturalOrder());
|
||||
}
|
||||
|
||||
String testCollectionsMinWithComparator() {
|
||||
return Collections.min(ImmutableSet.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
int testMinOfVarargs() {
|
||||
return Collections.min(Arrays.asList(1, 2), naturalOrder());
|
||||
}
|
||||
@@ -126,6 +143,19 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Comparators.min("a", "b", (a, b) -> 1));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testCollectionsMax() {
|
||||
return ImmutableSet.of(
|
||||
Collections.max(ImmutableList.of("foo")), Collections.max(ImmutableList.of("bar")));
|
||||
}
|
||||
|
||||
String testMaxOfArray() {
|
||||
return Collections.max(Arrays.asList(new String[0]), naturalOrder());
|
||||
}
|
||||
|
||||
String testCollectionsMaxWithComparator() {
|
||||
return Collections.max(ImmutableSet.of("foo", "bar"), naturalOrder());
|
||||
}
|
||||
|
||||
int testMaxOfVarargs() {
|
||||
return Collections.max(Arrays.asList(1, 2), naturalOrder());
|
||||
}
|
||||
|
||||
@@ -3,12 +3,26 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Path testPathOfUri() {
|
||||
return Paths.get(URI.create("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<Path> testPathOfString() {
|
||||
return ImmutableSet.of(Paths.get("foo"), Paths.get("bar", "baz", "qux"));
|
||||
}
|
||||
|
||||
Path testPathInstance() {
|
||||
return Path.of("foo").toFile().toPath();
|
||||
}
|
||||
|
||||
String testFilesReadStringWithCharset() throws IOException {
|
||||
return new String(Files.readAllBytes(Paths.get("foo")), StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
@@ -21,4 +35,8 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
File.createTempFile("foo", "bar"), File.createTempFile("baz", "qux", null));
|
||||
}
|
||||
|
||||
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
|
||||
return File.createTempFile("foo", "bar", new File("baz"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,26 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Path testPathOfUri() {
|
||||
return Path.of(URI.create("foo"));
|
||||
}
|
||||
|
||||
ImmutableSet<Path> testPathOfString() {
|
||||
return ImmutableSet.of(Path.of("foo"), Path.of("bar", "baz", "qux"));
|
||||
}
|
||||
|
||||
Path testPathInstance() {
|
||||
return Path.of("foo");
|
||||
}
|
||||
|
||||
String testFilesReadStringWithCharset() throws IOException {
|
||||
return Files.readString(Paths.get("foo"), StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
@@ -21,4 +35,8 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
Files.createTempFile("foo", "bar").toFile(), Files.createTempFile("baz", "qux").toFile());
|
||||
}
|
||||
|
||||
File testFilesCreateTempFileInCustomDirectoryToFile() throws IOException {
|
||||
return Files.createTempFile(new File("baz").toPath(), "foo", "bar").toFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(EnumSet.class, toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSetIterable() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.copyOf(Iterables.cycle(RoundingMode.UP)),
|
||||
ImmutableSet.copyOf(EnumSet.allOf(RoundingMode.class)));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetArraysAsList() {
|
||||
return ImmutableSet.copyOf(RoundingMode.values());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet1() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP), ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet2() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN),
|
||||
ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet3() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING),
|
||||
ImmutableSet.copyOf(EnumSet.of(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING)));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> 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<ImmutableSet<RoundingMode>> 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<RoundingMode> testSetsImmutableEnumSet6() {
|
||||
return ImmutableSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetVarArgs() {
|
||||
return ImmutableSet.copyOf(
|
||||
EnumSet.of(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN));
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
@@ -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<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(EnumSet.class, toImmutableSet());
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSetIterable() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(Iterables.cycle(RoundingMode.UP)),
|
||||
Sets.immutableEnumSet(EnumSet.allOf(RoundingMode.class)));
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetArraysAsList() {
|
||||
return Sets.immutableEnumSet(Arrays.asList(RoundingMode.values()));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet1() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP), Sets.immutableEnumSet(RoundingMode.UP));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet2() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN),
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> testSetsImmutableEnumSet3() {
|
||||
return ImmutableSet.of(
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING),
|
||||
Sets.immutableEnumSet(RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING));
|
||||
}
|
||||
|
||||
ImmutableSet<ImmutableSet<RoundingMode>> 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<ImmutableSet<RoundingMode>> 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<RoundingMode> testSetsImmutableEnumSet6() {
|
||||
return Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<RoundingMode> testSetsImmutableEnumSetVarArgs() {
|
||||
return Sets.immutableEnumSet(
|
||||
RoundingMode.UP,
|
||||
RoundingMode.DOWN,
|
||||
RoundingMode.CEILING,
|
||||
RoundingMode.FLOOR,
|
||||
RoundingMode.UNNECESSARY,
|
||||
RoundingMode.HALF_EVEN);
|
||||
}
|
||||
|
||||
ImmutableSet<BoundType> testStreamToImmutableEnumSet() {
|
||||
return Stream.of(BoundType.OPEN).collect(toImmutableEnumSet());
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
|
||||
Stream.<Integer>empty().collect(toImmutableMultiset()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableList.of(1).stream().collect(toImmutableMultiset()),
|
||||
|
||||
@@ -24,6 +24,7 @@ final class ImmutableMultisetRulesTest implements RefasterRuleCollectionTestCase
|
||||
return ImmutableMultiset.of(ImmutableMultiset.of(), ImmutableMultiset.of());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableMultiset<Integer>> testIterableToImmutableMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableMultiset.copyOf(ImmutableList.of(1)),
|
||||
|
||||
@@ -37,6 +37,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
|
||||
Stream.<Integer>empty().collect(toImmutableSortedMultiset(naturalOrder())));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableSortedMultiset.copyOf(naturalOrder(), ImmutableList.of(1)),
|
||||
|
||||
@@ -35,6 +35,7 @@ final class ImmutableSortedMultisetRulesTest implements RefasterRuleCollectionTe
|
||||
return ImmutableMultiset.of(ImmutableSortedMultiset.of(), ImmutableSortedMultiset.of());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableMultiset<ImmutableSortedMultiset<Integer>> testIterableToImmutableSortedMultiset() {
|
||||
return ImmutableMultiset.of(
|
||||
ImmutableSortedMultiset.copyOf(ImmutableList.of(1)),
|
||||
|
||||
@@ -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<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(ImmutableList.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf1() {
|
||||
return ImmutableSet.of(
|
||||
ImmutableSet.of(Tag.of("foo", "v1")), ImmutableList.of(Tag.of("bar", "v2")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> 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<Iterable<Tag>> 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<Iterable<Tag>> 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<Iterable<Tag>> 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")));
|
||||
}
|
||||
}
|
||||
@@ -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<Object> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(ImmutableList.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> testTagsOf1() {
|
||||
return ImmutableSet.of(Tags.of(Tag.of("foo", "v1")), Tags.of(Tag.of("bar", "v2")));
|
||||
}
|
||||
|
||||
ImmutableSet<Iterable<Tag>> 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<Iterable<Tag>> 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<Iterable<Tag>> 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<Iterable<Tag>> 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")));
|
||||
}
|
||||
}
|
||||
@@ -83,11 +83,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Optional.of("foo").orElseGet(() -> Optional.of("bar").orElseThrow());
|
||||
}
|
||||
|
||||
ImmutableSet<String> testOptionalOrElseGet() {
|
||||
ImmutableSet<String> testOptionalOrElse() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").orElse("bar"),
|
||||
Optional.of("baz").orElse(toString()),
|
||||
Optional.of("qux").orElse(String.valueOf(true)));
|
||||
Optional.of("foo").orElseGet(() -> "bar"), Optional.of("baz").orElseGet(() -> toString()));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testStreamFlatMapOptional() {
|
||||
|
||||
@@ -80,11 +80,9 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Optional.of("foo").or(() -> Optional.of("bar")).orElseThrow();
|
||||
}
|
||||
|
||||
ImmutableSet<String> testOptionalOrElseGet() {
|
||||
ImmutableSet<String> testOptionalOrElse() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").orElse("bar"),
|
||||
Optional.of("baz").orElse(toString()),
|
||||
Optional.of("qux").orElseGet(() -> String.valueOf(true)));
|
||||
Optional.of("foo").orElse("bar"), Optional.of("baz").orElseGet(() -> toString()));
|
||||
}
|
||||
|
||||
ImmutableSet<Object> testStreamFlatMapOptional() {
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.google.common.primitives.Floats;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.primitives.Shorts;
|
||||
import com.google.common.primitives.UnsignedInts;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Floats.class,
|
||||
Ints.class,
|
||||
Longs.class,
|
||||
Shorts.class);
|
||||
Shorts.class,
|
||||
UnsignedInts.class,
|
||||
UnsignedLongs.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testLessThan() {
|
||||
@@ -105,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<Integer> testIntegerParseUnsignedInt() {
|
||||
return ImmutableSet.of(UnsignedInts.parseUnsignedInt("1"), Integer.parseUnsignedInt("2", 10));
|
||||
}
|
||||
|
||||
ImmutableSet<Long> testLongParseUnsignedLong() {
|
||||
return ImmutableSet.of(UnsignedLongs.parseUnsignedLong("1"), Long.parseUnsignedLong("2", 10));
|
||||
}
|
||||
|
||||
int testIntegerParseUnsignedIntWithRadix() {
|
||||
return UnsignedInts.parseUnsignedInt("1", 2);
|
||||
}
|
||||
|
||||
long testLongParseUnsignedLongWithRadix() {
|
||||
return UnsignedLongs.parseUnsignedLong("1", 2);
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIntegerToUnsignedString() {
|
||||
return ImmutableSet.of(UnsignedInts.toString(1), Integer.toUnsignedString(2, 10));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testLongToUnsignedString() {
|
||||
return ImmutableSet.of(UnsignedLongs.toString(1), Long.toUnsignedString(2, 10));
|
||||
}
|
||||
|
||||
String testIntegerToUnsignedStringWithRadix() {
|
||||
return UnsignedInts.toString(1, 2);
|
||||
}
|
||||
|
||||
String testLongToUnsignedStringWithRadix() {
|
||||
return UnsignedLongs.toString(1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import com.google.common.primitives.Floats;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.primitives.Shorts;
|
||||
import com.google.common.primitives.UnsignedInts;
|
||||
import com.google.common.primitives.UnsignedLongs;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@@ -22,7 +24,9 @@ final class PrimitiveRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Floats.class,
|
||||
Ints.class,
|
||||
Longs.class,
|
||||
Shorts.class);
|
||||
Shorts.class,
|
||||
UnsignedInts.class,
|
||||
UnsignedLongs.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Boolean> testLessThan() {
|
||||
@@ -105,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<Integer> testIntegerParseUnsignedInt() {
|
||||
return ImmutableSet.of(Integer.parseUnsignedInt("1"), Integer.parseUnsignedInt("2"));
|
||||
}
|
||||
|
||||
ImmutableSet<Long> testLongParseUnsignedLong() {
|
||||
return ImmutableSet.of(Long.parseUnsignedLong("1"), Long.parseUnsignedLong("2"));
|
||||
}
|
||||
|
||||
int testIntegerParseUnsignedIntWithRadix() {
|
||||
return Integer.parseUnsignedInt("1", 2);
|
||||
}
|
||||
|
||||
long testLongParseUnsignedLongWithRadix() {
|
||||
return Long.parseUnsignedLong("1", 2);
|
||||
}
|
||||
|
||||
ImmutableSet<String> testIntegerToUnsignedString() {
|
||||
return ImmutableSet.of(Integer.toUnsignedString(1), Integer.toUnsignedString(2));
|
||||
}
|
||||
|
||||
ImmutableSet<String> testLongToUnsignedString() {
|
||||
return ImmutableSet.of(Long.toUnsignedString(1), Long.toUnsignedString(2));
|
||||
}
|
||||
|
||||
String testIntegerToUnsignedStringWithRadix() {
|
||||
return Integer.toUnsignedString(1, 2);
|
||||
}
|
||||
|
||||
String testLongToUnsignedStringWithRadix() {
|
||||
return Long.toUnsignedString(1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import static java.util.stream.Collectors.minBy;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -664,6 +665,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.fromFuture(CompletableFuture.completedFuture(null), true);
|
||||
}
|
||||
|
||||
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
|
||||
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxFromStreamSupplier() {
|
||||
return Flux.fromStream(Stream.of(1));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import static java.util.stream.Collectors.toCollection;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -644,6 +645,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true);
|
||||
}
|
||||
|
||||
Mono<String> testMonoFromFutureAsyncLoadingCacheGet() {
|
||||
return Mono.fromFuture(() -> ((AsyncLoadingCache<Integer, String>) null).get(0), true);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxFromStreamSupplier() {
|
||||
return Flux.fromStream(() -> Stream.of(1));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-experimental</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-guidelines</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
|
||||
@@ -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<String> 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<String> getSet(ErrorProneFlags errorProneFlags, String name) {
|
||||
return ImmutableSet.copyOf(getList(errorProneFlags, name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Arguments> getListTestCases() {
|
||||
/* { args, flag, expected } */
|
||||
private static Stream<Arguments> 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<String> args, String flag, ImmutableList<String> expected) {
|
||||
void getList(ImmutableList<String> args, String flag, ImmutableList<String> listed) {
|
||||
assertThat(Flags.getList(ErrorProneOptions.processArgs(args).getFlags(), flag))
|
||||
.containsExactlyElementsOf(expected);
|
||||
.containsExactlyElementsOf(listed);
|
||||
}
|
||||
|
||||
@MethodSource("getCollectionTestCases")
|
||||
@ParameterizedTest
|
||||
void getSet(ImmutableList<String> args, String flag, ImmutableList<String> listed) {
|
||||
assertThat(Flags.getSet(ErrorProneOptions.processArgs(args).getFlags(), flag))
|
||||
.containsExactlyElementsOf(ImmutableSet.copyOf(listed));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, VisitorState, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
121
pom.xml
121
pom.xml
@@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Picnic :: Error Prone Support</name>
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
<scm child.scm.developerConnection.inherit.append.path="false" child.scm.url.inherit.append.path="false">
|
||||
<developerConnection>scm:git:git@github.com:PicnicSupermarket/error-prone-support.git</developerConnection>
|
||||
<tag>v0.18.0</tag>
|
||||
<tag>HEAD</tag>
|
||||
<url>https://github.com/PicnicSupermarket/error-prone-support</url>
|
||||
</scm>
|
||||
<issueManagement>
|
||||
@@ -111,6 +111,9 @@
|
||||
but also prevents excessive memory usage by heavily parallelized
|
||||
local builds. -->
|
||||
-Xmx${argLine.xmx}
|
||||
<!-- Configure the Byte Buddy Java agent used by Mockito to create
|
||||
mocks. -->
|
||||
-javaagent:${org.mockito:mockito-core:jar}
|
||||
<!-- This argument cannot be set through Surefire's
|
||||
'systemPropertyVariables' configuration setting. Setting the file
|
||||
encoding is necessary because forked unit test invocations
|
||||
@@ -148,7 +151,7 @@
|
||||
<groupId.error-prone>com.google.errorprone</groupId.error-prone>
|
||||
<!-- The build timestamp is derived from the most recent commit
|
||||
timestamp in support of reproducible builds. -->
|
||||
<project.build.outputTimestamp>2024-08-11T13:04:41Z</project.build.outputTimestamp>
|
||||
<project.build.outputTimestamp>2024-11-03T15:58:19Z</project.build.outputTimestamp>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<!-- Glob pattern identifying Refaster rule definition files. These
|
||||
Java classes don't contain "regular" code, and thus require special
|
||||
@@ -208,16 +211,16 @@
|
||||
<version.auto-value>1.11.0</version.auto-value>
|
||||
<version.error-prone>${version.error-prone-orig}</version.error-prone>
|
||||
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
|
||||
<version.error-prone-orig>2.30.0</version.error-prone-orig>
|
||||
<version.error-prone-slf4j>0.1.25</version.error-prone-slf4j>
|
||||
<version.error-prone-orig>2.35.1</version.error-prone-orig>
|
||||
<version.error-prone-slf4j>0.1.28</version.error-prone-slf4j>
|
||||
<version.guava-beta-checker>1.0</version.guava-beta-checker>
|
||||
<version.jdk>17</version.jdk>
|
||||
<version.maven>3.9.5</version.maven>
|
||||
<version.mockito>5.12.0</version.mockito>
|
||||
<version.maven>3.9.9</version.maven>
|
||||
<version.mockito>5.14.2</version.mockito>
|
||||
<version.nopen-checker>1.0.1</version.nopen-checker>
|
||||
<version.nullaway>0.11.1</version.nullaway>
|
||||
<version.nullaway>0.12.1</version.nullaway>
|
||||
<version.pitest-git>1.1.4</version.pitest-git>
|
||||
<version.rewrite-templating>1.12.3</version.rewrite-templating>
|
||||
<version.rewrite-templating>1.16.3</version.rewrite-templating>
|
||||
<version.surefire>3.2.3</version.surefire>
|
||||
</properties>
|
||||
|
||||
@@ -296,10 +299,15 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>2.17.2</version>
|
||||
<version>2.18.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.1.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
@@ -328,7 +336,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
<version>1.23.0</version>
|
||||
<version>1.24.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
@@ -338,7 +346,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava-bom</artifactId>
|
||||
<version>33.2.1-jre</version>
|
||||
<version>33.3.1-jre</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -357,10 +365,15 @@
|
||||
<artifactId>nullaway</artifactId>
|
||||
<version>${version.nullaway}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-core</artifactId>
|
||||
<version>1.13.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-bom</artifactId>
|
||||
<version>2023.0.8</version>
|
||||
<version>2023.0.11</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -377,7 +390,7 @@
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>2.2.22</version>
|
||||
<version>2.2.25</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
@@ -412,7 +425,7 @@
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.14.18</version>
|
||||
<version>1.15.10</version>
|
||||
</dependency>
|
||||
<!-- Specified so that Renovate will file Maven upgrade PRs, which
|
||||
subsequently will cause `maven-enforcer-plugin` to require that
|
||||
@@ -437,7 +450,7 @@
|
||||
<dependency>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-qual</artifactId>
|
||||
<version>3.46.0</version>
|
||||
<version>3.48.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@@ -457,7 +470,7 @@
|
||||
<dependency>
|
||||
<groupId>org.junit</groupId>
|
||||
<artifactId>junit-bom</artifactId>
|
||||
<version>5.10.3</version>
|
||||
<version>5.11.3</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -471,7 +484,14 @@
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-core</artifactId>
|
||||
<version>5.1.3</version>
|
||||
<version>5.2.0</version>
|
||||
</dependency>
|
||||
<!-- XXX: Drop this `rewrite-java-17` version declaration once
|
||||
`rewrite-recipe-bom` pulls in version 8.39.1 or greater. -->
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
<artifactId>rewrite-java-17</artifactId>
|
||||
<version>8.38.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openrewrite</groupId>
|
||||
@@ -481,7 +501,7 @@
|
||||
<dependency>
|
||||
<groupId>org.openrewrite.recipe</groupId>
|
||||
<artifactId>rewrite-recipe-bom</artifactId>
|
||||
<version>2.16.0</version>
|
||||
<version>2.21.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -495,19 +515,19 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>6.1.11</version>
|
||||
<version>6.1.14</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.3.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>6.3.1</version>
|
||||
<version>6.3.4</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -518,6 +538,17 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- To avoid repetition of fairly complex configuration, Surefire
|
||||
is configured to always load Mockito's Java agent. Declaring the
|
||||
associated dependency here avoids additional repetition, and prevents
|
||||
a slightly obscure error that happens if the dependency is absent. -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
@@ -562,7 +593,7 @@
|
||||
<plugin>
|
||||
<groupId>com.spotify.fmt</groupId>
|
||||
<artifactId>fmt-maven-plugin</artifactId>
|
||||
<version>2.22.1</version>
|
||||
<version>2.25</version>
|
||||
<configuration>
|
||||
<additionalSourceDirectories>
|
||||
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
|
||||
@@ -582,7 +613,7 @@
|
||||
<plugin>
|
||||
<groupId>de.thetaphi</groupId>
|
||||
<artifactId>forbiddenapis</artifactId>
|
||||
<version>3.7</version>
|
||||
<version>3.8</version>
|
||||
<configuration>
|
||||
<bundledSignatures>
|
||||
<bundledSignature>jdk-internal</bundledSignature>
|
||||
@@ -648,7 +679,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<checkstyleRules>
|
||||
<!-- We only enable rules that are not enforced by
|
||||
@@ -897,7 +928,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.17.0</version>
|
||||
<version>10.20.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.spring.nohttp</groupId>
|
||||
@@ -1001,7 +1032,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.7.1</version>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<!-- XXX: Drop `ignoreAllNonTestScoped` once
|
||||
https://issues.apache.org/jira/browse/MNG-6058 is
|
||||
@@ -1012,11 +1043,19 @@
|
||||
<ignoreDirect>false</ignoreDirect>
|
||||
<ignoreNonCompile>true</ignoreNonCompile>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>set-additional-properties</id>
|
||||
<goals>
|
||||
<goal>properties</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.1.3</version>
|
||||
<configuration>
|
||||
<retryFailedDeploymentCount>3</retryFailedDeploymentCount>
|
||||
</configuration>
|
||||
@@ -1094,7 +1133,7 @@
|
||||
<dependency>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>extra-enforcer-rules</artifactId>
|
||||
<version>1.8.0</version>
|
||||
<version>1.9.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@@ -1109,7 +1148,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<version>3.2.7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
@@ -1122,7 +1161,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -1188,7 +1227,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<additionalJOptions>
|
||||
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</additionalJOption>
|
||||
@@ -1239,7 +1278,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
<version>3.21.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -1258,7 +1297,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<version>3.5.2</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Test.java</include>
|
||||
@@ -1469,7 +1508,7 @@
|
||||
<plugin>
|
||||
<groupId>org.kordamp.maven</groupId>
|
||||
<artifactId>pomchecker-maven-plugin</artifactId>
|
||||
<version>1.11.0</version>
|
||||
<version>1.14.0</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<release>false</release>
|
||||
@@ -1487,7 +1526,7 @@
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.16.1</version>
|
||||
<version>1.17.0</version>
|
||||
<configuration>
|
||||
<excludedClasses>
|
||||
<!-- AutoValue generated classes. -->
|
||||
@@ -1550,6 +1589,10 @@
|
||||
<groupId>io.github.git-commit-id</groupId>
|
||||
<artifactId>git-commit-id-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
@@ -1929,6 +1972,12 @@
|
||||
<!-- XXX: Enable this once we open-source
|
||||
this library. -->
|
||||
-Xep:BetaApi:OFF
|
||||
<!-- This check flags bad member names, but
|
||||
also type names that we may want to accept.
|
||||
Consider contributing a flag to toggle this
|
||||
behavior. See
|
||||
https://github.com/google/error-prone/issues/4616 -->
|
||||
-Xep:IdentifierName:OFF
|
||||
<!-- We don't target JDK 7. -->
|
||||
-Xep:Java7ApiChecker:OFF
|
||||
<!-- We don't target JDK 8. -->
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-runner</artifactId>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-support</artifactId>
|
||||
@@ -88,10 +88,5 @@
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refaster.matchers;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ArrayAccessTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
@@ -9,34 +10,19 @@ import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.ParenthesizedTree;
|
||||
import com.sun.source.tree.TypeCastTree;
|
||||
import com.sun.source.tree.UnaryTree;
|
||||
|
||||
/** A matcher of expressions that likely require little to no computation. */
|
||||
public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree> {
|
||||
/** A matcher of expressions that may a non-trivial amount of computation. */
|
||||
public final class RequiresComputation implements Matcher<ExpressionTree> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link IsLikelyTrivialComputation} instance. */
|
||||
public IsLikelyTrivialComputation() {}
|
||||
/** Instantiates a new {@link RequiresComputation} instance. */
|
||||
public RequiresComputation() {}
|
||||
|
||||
@Override
|
||||
public boolean matches(ExpressionTree expressionTree, VisitorState state) {
|
||||
if (expressionTree instanceof MethodInvocationTree methodInvocation) {
|
||||
// XXX: Method invocations are generally *not* trivial computations, but we make an exception
|
||||
// for nullary method invocations on the result of a trivial computation. This exception
|
||||
// allows this `Matcher` to by the `OptionalOrElseGet` Refaster rule, such that it does not
|
||||
// suggest the introduction of lambda expressions that are better expressed as method
|
||||
// references. Once the `MethodReferenceUsage` bug checker is production-ready, this exception
|
||||
// should be removed. (But at that point, instead defining a `RequiresComputation` matcher may
|
||||
// be more appropriate.)
|
||||
if (methodInvocation.getArguments().isEmpty()
|
||||
&& matches(methodInvocation.getMethodSelect())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return matches(expressionTree);
|
||||
}
|
||||
|
||||
@@ -44,11 +30,11 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
|
||||
// Depending on feedback such trees may be matched in the future.
|
||||
private static boolean matches(ExpressionTree expressionTree) {
|
||||
if (expressionTree instanceof ArrayAccessTree arrayAccess) {
|
||||
return matches(arrayAccess.getExpression()) && matches(arrayAccess.getIndex());
|
||||
return matches(arrayAccess.getExpression()) || matches(arrayAccess.getIndex());
|
||||
}
|
||||
|
||||
if (expressionTree instanceof LiteralTree) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expressionTree instanceof LambdaExpressionTree) {
|
||||
@@ -56,11 +42,14 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
|
||||
* Lambda expressions encapsulate computations, but their definition does not involve
|
||||
* significant computation.
|
||||
*/
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expressionTree instanceof IdentifierTree) {
|
||||
return true;
|
||||
// XXX: Generally identifiers don't by themselves represent a computation, though they may be
|
||||
// a stand-in for one if they are a Refaster template method argument. Can we identify such
|
||||
// cases, also when the `Matcher` is invoked by Refaster?
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expressionTree instanceof MemberReferenceTree memberReference) {
|
||||
@@ -80,11 +69,11 @@ public final class IsLikelyTrivialComputation implements Matcher<ExpressionTree>
|
||||
}
|
||||
|
||||
if (expressionTree instanceof UnaryTree unary) {
|
||||
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement are not
|
||||
// trivial.
|
||||
// XXX: Arguably side-effectful options such as pre- and post-increment and -decrement
|
||||
// represent non-trivial computations.
|
||||
return matches(unary.getExpression());
|
||||
}
|
||||
|
||||
return false;
|
||||
return ASTHelpers.constValue(expressionTree) == null;
|
||||
}
|
||||
}
|
||||
@@ -8,54 +8,69 @@ import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class IsLikelyTrivialComputationTest {
|
||||
final class RequiresComputationTest {
|
||||
@Test
|
||||
void matches() {
|
||||
CompilationTestHelper.newInstance(MatcherTestChecker.class, getClass())
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.io.OutputStream;",
|
||||
"import java.util.Comparator;",
|
||||
"import java.util.function.Predicate;",
|
||||
"",
|
||||
"class A {",
|
||||
" String negative1() {",
|
||||
" return String.valueOf(1);",
|
||||
" int negative1() {",
|
||||
" int[] arr = new int[0];",
|
||||
" return arr[0];",
|
||||
" }",
|
||||
"",
|
||||
" String negative2() {",
|
||||
" return toString().toString();",
|
||||
" return null;",
|
||||
" }",
|
||||
"",
|
||||
" String negative3() {",
|
||||
" return \"foo\" + toString();",
|
||||
" boolean negative3() {",
|
||||
" return false;",
|
||||
" }",
|
||||
"",
|
||||
" byte negative4() {",
|
||||
" return \"foo\".getBytes()[0];",
|
||||
" int negative4() {",
|
||||
" return 0;",
|
||||
" }",
|
||||
"",
|
||||
" int negative5() {",
|
||||
" int[] arr = new int[0];",
|
||||
" return arr[hashCode()];",
|
||||
" String negative5() {",
|
||||
" return \"foo\" + \"bar\";",
|
||||
" }",
|
||||
"",
|
||||
" int negative6() {",
|
||||
" return 1 * 2;",
|
||||
" Predicate<String> negative6() {",
|
||||
" return v -> \"foo\".equals(v);",
|
||||
" }",
|
||||
"",
|
||||
" Predicate<String> negative7() {",
|
||||
" return toString()::equals;",
|
||||
" A negative7() {",
|
||||
" return this;",
|
||||
" }",
|
||||
"",
|
||||
" String negative8() {",
|
||||
" return (toString());",
|
||||
" Predicate<String> negative8() {",
|
||||
" return \"foo\"::equals;",
|
||||
" }",
|
||||
"",
|
||||
" Object negative9() {",
|
||||
" return (Object) toString();",
|
||||
" OutputStream negative9() {",
|
||||
" return System.out;",
|
||||
" }",
|
||||
"",
|
||||
" int negative10() {",
|
||||
" return -hashCode();",
|
||||
" A negative10() {",
|
||||
" return (this);",
|
||||
" }",
|
||||
"",
|
||||
" Object negative11() {",
|
||||
" return (Object) this;",
|
||||
" }",
|
||||
"",
|
||||
" boolean negative12() {",
|
||||
" boolean[] arr = new boolean[0];",
|
||||
" return !arr[0];",
|
||||
" }",
|
||||
"",
|
||||
" String negative13() {",
|
||||
" return \"foo\" + 0;",
|
||||
" }",
|
||||
"",
|
||||
" String positive1() {",
|
||||
@@ -68,68 +83,63 @@ final class IsLikelyTrivialComputationTest {
|
||||
" return this.toString();",
|
||||
" }",
|
||||
"",
|
||||
" int positive3() {",
|
||||
" int[] arr = new int[0];",
|
||||
" String positive3() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return arr[0];",
|
||||
" return String.valueOf(1);",
|
||||
" }",
|
||||
"",
|
||||
" String positive4() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return null;",
|
||||
" return toString().toString();",
|
||||
" }",
|
||||
"",
|
||||
" boolean positive5() {",
|
||||
" String positive5() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return false;",
|
||||
" return \"foo\" + toString();",
|
||||
" }",
|
||||
"",
|
||||
" int positive6() {",
|
||||
" byte positive6() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return 0;",
|
||||
" return \"foo\".getBytes()[0];",
|
||||
" }",
|
||||
"",
|
||||
" String positive7() {",
|
||||
" int positive7() {",
|
||||
" int[] arr = new int[0];",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return \"foo\" + \"bar\";",
|
||||
" return arr[hashCode()];",
|
||||
" }",
|
||||
"",
|
||||
" Predicate<String> positive8() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return v -> \"foo\".equals(v);",
|
||||
" return toString()::equals;",
|
||||
" }",
|
||||
"",
|
||||
" A positive9() {",
|
||||
" Comparator<String> positive9() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return this;",
|
||||
" return toString().CASE_INSENSITIVE_ORDER;",
|
||||
" }",
|
||||
"",
|
||||
" Predicate<String> positive10() {",
|
||||
" String positive10() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return \"foo\"::equals;",
|
||||
" return (toString());",
|
||||
" }",
|
||||
"",
|
||||
" A positive11() {",
|
||||
" Object positive11() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return (this);",
|
||||
" return (Object) toString();",
|
||||
" }",
|
||||
"",
|
||||
" Object positive12() {",
|
||||
" int positive12() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return (Object) this;",
|
||||
" }",
|
||||
"",
|
||||
" boolean positive13() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" return !false;",
|
||||
" return -hashCode();",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/** A {@link BugChecker} that simply delegates to {@link IsLikelyTrivialComputation}. */
|
||||
/** A {@link BugChecker} that simply delegates to {@link RequiresComputation}. */
|
||||
@BugPattern(
|
||||
summary = "Flags return statement expressions matched by `IsLikelyTrivialComputation`",
|
||||
summary = "Flags return statement expressions matched by `RequiresComputation`",
|
||||
severity = ERROR)
|
||||
public static final class MatcherTestChecker extends AbstractMatcherTestChecker {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -141,7 +151,7 @@ final class IsLikelyTrivialComputationTest {
|
||||
super(
|
||||
(expressionTree, state) ->
|
||||
state.getPath().getParentPath().getLeaf() instanceof ReturnTree
|
||||
&& new IsLikelyTrivialComputation().matches(expressionTree, state));
|
||||
&& new RequiresComputation().matches(expressionTree, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.18.0</version>
|
||||
<version>0.19.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
# An overview of Error Prone Support releases, along with compatible Error
|
||||
# Prone releases. This data was generated by `generate-version-compatibility-overview.sh`.
|
||||
releases:
|
||||
- version: 0.18.0
|
||||
compatible:
|
||||
- "2.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"
|
||||
|
||||
@@ -12,7 +12,7 @@ set -e -u -o pipefail
|
||||
|
||||
# Currently all released Error Prone Support versions are compatible with Java
|
||||
# 17.
|
||||
java_version=17.0.10-tem
|
||||
java_version=17.0.13-tem
|
||||
(set +u && echo n | sdk install java "${java_version}")
|
||||
sdk use java "${java_version}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user