Compare commits

..

5 Commits

Author SHA1 Message Date
Benedek Halasi
24dbe77522 xxx 2024-04-09 09:56:06 +02:00
Rick Ossendrijver
2311bd54b1 Add openrewrite dependencies 2024-04-09 09:00:28 +02:00
Rick Ossendrijver
fcd708e00f Update the workshop branch and apply best practices of EPS 2024-04-05 10:08:52 +02:00
Rick Ossendrijver
244ac55a01 Post-rebase fix 2024-04-05 07:58:59 +02:00
Rick Ossendrijver
ec36dcab72 Introduce workshop module and configure integration-test 2024-04-05 07:58:58 +02:00
84 changed files with 2046 additions and 864 deletions

View File

@@ -7,10 +7,11 @@ permissions:
contents: read
jobs:
build:
if: github.repository == 'PicnicSupermarket/error-prone-support'
strategy:
matrix:
os: [ ubuntu-22.04 ]
jdk: [ 17.0.10, 21.0.2, 22.0.2 ]
jdk: [ 17.0.10, 21.0.2 ]
distribution: [ temurin ]
experimental: [ false ]
include:
@@ -26,15 +27,13 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.adoptium.net:443
github.com:443
jitpack.io:443
objects.githubusercontent.com:443
repo.maven.apache.org:443
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
@@ -42,7 +41,7 @@ jobs:
# additionally enabling all checks defined in this project and any Error
# Prone checks available only from other artifact repositories.
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
java-version: ${{ matrix.jdk }}
java-distribution: ${{ matrix.distribution }}

View File

@@ -13,6 +13,7 @@ permissions:
contents: read
jobs:
analyze:
if: github.repository == 'PicnicSupermarket/error-prone-support'
strategy:
matrix:
language: [ java, ruby ]
@@ -22,31 +23,30 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.adoptium.net:443
api.github.com:443
github.com:443
objects.githubusercontent.com:443
repo.maven.apache.org:443
uploads.github.com:443
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
java-version: 17.0.10
java-distribution: temurin
maven-version: 3.9.6
- name: Initialize CodeQL
uses: github/codeql-action/init@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
languages: ${{ matrix.language }}
- name: Perform minimal build
if: matrix.language == 'java'
run: mvn -T1C clean package -DskipTests -Dverification.skip
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
category: /language:${{ matrix.language }}

View File

@@ -9,10 +9,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
if: github.repository == 'PicnicSupermarket/error-prone-support'
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
@@ -39,10 +40,10 @@ jobs:
www.youtube.com:443
youtrack.jetbrains.com:443
- name: Check out code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- uses: ruby/setup-ruby@6bd3d993c602f6b675728ebaecb2b569ff86e99b # v1.174.0
- uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899 # v1.173.0
with:
working-directory: ./website
bundler-cache: true
@@ -74,7 +75,7 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block

View File

@@ -14,6 +14,7 @@ permissions:
contents: read
jobs:
analyze:
if: github.repository == 'PicnicSupermarket/error-prone-support'
permissions:
contents: read
security-events: write
@@ -21,30 +22,31 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.github.com:443
api.osv.dev:443
api.scorecard.dev:443
api.securityscorecards.dev:443
fulcio.sigstore.dev:443
github.com:443
oss-fuzz-build-logs.storage.googleapis.com:443
*.sigstore.dev:443
rekor.sigstore.dev:443
tuf-repo-cdn.sigstore.dev:443
www.bestpractices.dev:443
- name: Check out code
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: Run OpenSSF Scorecard analysis
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
publish_results: ${{ github.ref == 'refs/heads/master' }}
- name: Update GitHub's code scanning dashboard
uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
with:
sarif_file: results.sarif

View File

@@ -9,20 +9,19 @@ permissions:
contents: read
jobs:
analyze-pr:
if: github.repository == 'PicnicSupermarket/error-prone-support'
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.adoptium.net:443
github.com:443
objects.githubusercontent.com:443
repo.maven.apache.org:443
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
checkout-fetch-depth: 2
java-version: 17.0.10
@@ -38,7 +37,7 @@ jobs:
- name: Aggregate Pitest reports
run: mvn pitest-git:aggregate -DkilledEmoji=":tada:" -DmutantEmoji=":zombie:" -DtrailingText="Mutation testing report by [Pitest](https://pitest.org/). Review any surviving mutants by inspecting the line comments under [_Files changed_](${{ github.event.number }}/files)."
- name: Upload Pitest reports as artifact
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: pitest-reports
path: ./target/pit-reports-ci

View File

@@ -11,7 +11,7 @@ permissions:
actions: read
jobs:
update-pr:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'PicnicSupermarket/error-prone-support' }}
permissions:
actions: read
checks: write
@@ -20,18 +20,16 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.adoptium.net:443
api.github.com:443
github.com:443
objects.githubusercontent.com:443
repo.maven.apache.org:443
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
java-version: 17.0.10
java-distribution: temurin

View File

@@ -9,31 +9,29 @@ name: "Integration tests"
on:
issue_comment:
types: [ created ]
pull_request:
push:
permissions:
contents: read
jobs:
run-integration-tests:
name: On-demand integration test
if: |
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.adoptium.net:443
checkstyle.org:443
github.com:443
objects.githubusercontent.com:443
oss.sonatype.org:443
raw.githubusercontent.com:443
repo.maven.apache.org:443
repository.sonatype.org:443
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
java-version: 17.0.10
@@ -45,7 +43,7 @@ jobs:
run: xvfb-run ./integration-tests/checkstyle.sh "${{ runner.temp }}/artifacts"
- name: Upload artifacts on failure
if: ${{ failure() }}
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: integration-test-checkstyle
path: "${{ runner.temp }}/artifacts"

View File

@@ -19,23 +19,19 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
analysis-sensorcache-eu-central-1-prod.s3.amazonaws.com:443
api.adoptium.net:443
api.nuget.org:443
ea6ne4j2sb.execute-api.eu-central-1.amazonaws.com:443
github.com:443
objects.githubusercontent.com:443
repo.maven.apache.org:443
sc-cleancode-sensorcache-eu-central-1-prod.s3.amazonaws.com:443
*.sonarcloud.io:443
scanner.sonarcloud.io:443
sonarcloud.io:443
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@489441643219d2b93ee2a127b2402eb640a1b947 # v1.13.0
uses: s4u/setup-maven-action@6d44c18d67d9e1549907b8815efa5e4dada1801b # v1.12.0
with:
checkout-fetch-depth: 0
java-version: 17.0.10

View File

@@ -12,7 +12,7 @@
"separateMinorPatch": true
},
{
"matchPackageNames": [
"matchDepNames": [
"dawidd6/action-download-artifact",
"github/codeql-action",
"ruby/setup-ruby"

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -122,11 +122,6 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>

View File

@@ -232,7 +232,7 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
@Override
public @Nullable Void visitReturn(ReturnTree node, @Nullable Void unused) {
returnExpressions.add(node.getExpression());
return super.visitReturn(node, null);
return super.visitReturn(node, unused);
}
@Override

View File

@@ -192,7 +192,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
@Override
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
nodes.add(ImmutableList.of(node.getName().toString()));
return super.visitIdentifier(node, null);
return super.visitIdentifier(node, unused);
}
@Override
@@ -203,13 +203,13 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
? STRING_ARGUMENT_SPLITTER.splitToStream(str).collect(toImmutableList())
: ImmutableList.of(String.valueOf(value)));
return super.visitLiteral(node, null);
return super.visitLiteral(node, unused);
}
@Override
public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString()));
return super.visitPrimitiveType(node, null);
return super.visitPrimitiveType(node, unused);
}
}.scan(array, null);

View File

@@ -210,7 +210,7 @@ public final class NonStaticImport extends BugChecker implements CompilationUnit
}
}
return super.visitIdentifier(node, null);
return super.visitIdentifier(node, unused);
}
}.scan(tree, null);
}

View File

@@ -3,7 +3,6 @@ package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.function.Function;
import java.util.function.Predicate;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -38,12 +37,7 @@ final class ClassRules {
}
}
/**
* Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require
* naming a variable.
*/
// XXX: Once the `ClassReferenceIsInstancePredicate` rule is dropped, rename this rule to just
// `ClassIsInstancePredicate`.
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
static final class ClassLiteralIsInstancePredicate<T, S> {
@BeforeTemplate
Predicate<S> before() {
@@ -56,11 +50,7 @@ final class ClassRules {
}
}
/**
* Prefer {@link Class#isInstance(Object)} method references over lambda expressions that require
* naming a variable.
*/
// XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default.
/** Prefer {@link Class#isInstance(Object)} method references over more verbose alternatives. */
static final class ClassReferenceIsInstancePredicate<T, S> {
@BeforeTemplate
Predicate<S> before(Class<T> clazz) {
@@ -72,39 +62,4 @@ final class ClassRules {
return clazz::isInstance;
}
}
/**
* 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.
*/
// XXX: Drop this rule once the `MethodReferenceUsage` rule is enabled by default.
static final class ClassReferenceCast<T, S> {
@BeforeTemplate
Function<T, S> before(Class<? extends S> clazz) {
return o -> clazz.cast(o);
}
@AfterTemplate
Function<T, S> after(Class<? extends S> clazz) {
return clazz::cast;
}
}
}

View File

@@ -8,9 +8,7 @@ import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.NotMatches;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -23,7 +21,6 @@ import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsRefasterAsVarargs;
/** Refaster rules related to expressions dealing with (arbitrary) collections. */
// XXX: There are other Guava `Iterables` methods that should not be called if the input is known to
@@ -187,24 +184,6 @@ final class CollectionRules {
}
}
/** Don't unnecessarily call {@link Stream#distinct()} on an already-unique stream of elements. */
// XXX: This rule assumes that the `Set` relies on `Object#equals`, rather than a custom
// equivalence relation.
// XXX: Expressions that drop or reorder elements from the stream, such as `.filter`, `.skip` and
// `sorted`, can similarly be simplified. Covering all cases is better done using an Error Prone
// check.
static final class SetStream<T> {
@BeforeTemplate
Stream<?> before(Set<T> set) {
return set.stream().distinct();
}
@AfterTemplate
Stream<?> after(Set<T> set) {
return set.stream();
}
}
/** Prefer {@link ArrayList#ArrayList(Collection)} over the Guava alternative. */
@SuppressWarnings(
"NonApiType" /* Matching against `List` would unnecessarily constrain the rule. */)
@@ -297,23 +276,6 @@ final class CollectionRules {
}
}
/** Prefer {@link Arrays#asList(Object[])} over more contrived alternatives. */
// XXX: Consider moving this rule to `ImmutableListRules` and having it suggest
// `ImmutableList#copyOf`. That would retain immutability, at the cost of no longer handling
// `null`s.
static final class ArraysAsList<T> {
// XXX: This expression produces an unmodifiable list, while the alternative doesn't.
@BeforeTemplate
List<T> before(@NotMatches(IsRefasterAsVarargs.class) T[] array) {
return Arrays.stream(array).toList();
}
@AfterTemplate
List<T> after(T[] array) {
return Arrays.asList(array);
}
}
/** Prefer calling {@link Collection#toArray()} over more contrived alternatives. */
static final class CollectionToArray<T> {
@BeforeTemplate

View File

@@ -290,7 +290,7 @@ final class ComparatorRules {
static final class MinOfPairCustomOrder<T> {
@BeforeTemplate
@SuppressWarnings("java:S1067" /* The conditional operators are independent. */)
T before(T value1, T value2, Comparator<? super T> cmp) {
T before(T value1, T value2, Comparator<T> cmp) {
return Refaster.anyOf(
cmp.compare(value1, value2) <= 0 ? value1 : value2,
cmp.compare(value1, value2) > 0 ? value2 : value1,
@@ -305,7 +305,7 @@ final class ComparatorRules {
}
@AfterTemplate
T after(T value1, T value2, Comparator<? super T> cmp) {
T after(T value1, T value2, Comparator<T> cmp) {
return Comparators.min(value1, value2, cmp);
}
}
@@ -357,7 +357,7 @@ final class ComparatorRules {
static final class MaxOfPairCustomOrder<T> {
@BeforeTemplate
@SuppressWarnings("java:S1067" /* The conditional operators are independent. */)
T before(T value1, T value2, Comparator<? super T> cmp) {
T before(T value1, T value2, Comparator<T> cmp) {
return Refaster.anyOf(
cmp.compare(value1, value2) >= 0 ? value1 : value2,
cmp.compare(value1, value2) < 0 ? value2 : value1,
@@ -372,7 +372,7 @@ final class ComparatorRules {
}
@AfterTemplate
T after(T value1, T value2, Comparator<? super T> cmp) {
T after(T value1, T value2, Comparator<T> cmp) {
return Comparators.max(value1, value2, cmp);
}
}

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.function.Predicate.isEqual;
import static java.util.function.Predicate.not;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -20,9 +19,9 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
final class EqualityRules {
private EqualityRules() {}
/** Prefer reference-based equality for enums. */
// Primitive value comparisons are not matched, because Error Prone flags those out of the box.
static final class EnumReferenceEquality<T extends Enum<T>> {
/** Prefer reference-based quality for enums. */
// Primitive value comparisons are not listed, because Error Prone flags those out of the box.
static final class PrimitiveOrReferenceEquality<T extends Enum<T>> {
/**
* Enums can be compared by reference. It is safe to do so even in the face of refactorings,
* because if the type is ever converted to a non-enum, then Error-Prone will complain about any
@@ -44,20 +43,6 @@ final class EqualityRules {
}
}
/** Prefer reference-based equality for enums. */
static final class EnumReferenceEqualityLambda<T extends Enum<T>> {
@BeforeTemplate
Predicate<T> before(T e) {
return Refaster.anyOf(isEqual(e), e::equals);
}
@AfterTemplate
@SuppressWarnings("java:S1698" /* Reference comparison is valid for enums. */)
Predicate<T> after(T e) {
return v -> v == e;
}
}
/** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
// If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.

View File

@@ -2,15 +2,12 @@ package tech.picnic.errorprone.refasterrules;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with files. */
@@ -43,24 +40,4 @@ final class FileRules {
return Files.readString(path);
}
}
/**
* Prefer {@link Files#createTempFile(String, String, FileAttribute[])} over alternatives that
* create files with more liberal permissions.
*/
static final class FilesCreateTempFileToFile {
@BeforeTemplate
@SuppressWarnings("java:S5443" /* This violation will be rewritten. */)
File before(String prefix, String suffix) throws IOException {
return Refaster.anyOf(
File.createTempFile(prefix, suffix), File.createTempFile(prefix, suffix, null));
}
@AfterTemplate
@SuppressWarnings(
"java:S5443" /* On POSIX systems the file will only have user read-write permissions. */)
File after(String prefix, String suffix) throws IOException {
return Files.createTempFile(prefix, suffix).toFile();
}
}
}

View File

@@ -302,22 +302,16 @@ import tech.picnic.errorprone.refaster.annotation.TypeMigration;
final class JUnitToAssertJRules {
private JUnitToAssertJRules() {}
static final class Fail<T> {
static final class ThrowNewAssertionError {
@BeforeTemplate
T before() {
return Assertions.fail();
void before() {
Assertions.fail();
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once
// https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically
// importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's
// `fail`. Note that combining Error Prone's `RemoveUnusedImports` and
// `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the
// method to be imported statically if possible; just in a less efficient manner.
@AfterTemplate
@DoNotCall
T after() {
return fail();
void after() {
throw new AssertionError();
}
}
@@ -327,7 +321,12 @@ final class JUnitToAssertJRules {
return Assertions.fail(message);
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment.
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once
// https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically
// importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's
// `fail`. Note that combining Error Prone's `RemoveUnusedImports` and
// `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the
// method to be imported statically if possible; just in a less efficient manner.
@AfterTemplate
T after(String message) {
return fail(message);
@@ -340,24 +339,28 @@ final class JUnitToAssertJRules {
return Assertions.fail(message, throwable);
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment.
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` once
// https://github.com/google/error-prone/pull/3584 is resolved. Until that time, statically
// importing AssertJ's `fail` is likely to clash with an existing static import of JUnit's
// `fail`. Note that combining Error Prone's `RemoveUnusedImports` and
// `UnnecessarilyFullyQualified` checks and our `StaticImport` check will anyway cause the
// method to be imported statically if possible; just in a less efficient manner.
@AfterTemplate
T after(String message, Throwable throwable) {
return fail(message, throwable);
}
}
static final class FailWithThrowable<T> {
static final class FailWithThrowable {
@BeforeTemplate
T before(Throwable throwable) {
return Assertions.fail(throwable);
void before(Throwable throwable) {
Assertions.fail(throwable);
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)`. See `Fail` comment.
@AfterTemplate
@DoNotCall
T after(Throwable throwable) {
return fail(throwable);
void after(Throwable throwable) {
throw new AssertionError(throwable);
}
}

View File

@@ -27,19 +27,6 @@ import tech.picnic.errorprone.refaster.matchers.IsLikelyTrivialComputation;
final class OptionalRules {
private OptionalRules() {}
/** Prefer {@link Optional#empty()} over the more contrived alternative. */
static final class OptionalEmpty<T> {
@BeforeTemplate
Optional<T> before() {
return Optional.ofNullable(null);
}
@AfterTemplate
Optional<T> after() {
return Optional.empty();
}
}
static final class OptionalOfNullable<T> {
// XXX: Refaster should be smart enough to also rewrite occurrences in which there are
// parentheses around the null check, but that's currently not the case. Try to fix that.

View File

@@ -3,6 +3,7 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.MoreCollectors.toOptional;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
@@ -33,7 +34,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -41,7 +41,6 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -53,6 +52,7 @@ import reactor.util.context.Context;
import reactor.util.function.Tuple2;
import tech.picnic.errorprone.refaster.annotation.Description;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.annotation.Severity;
import tech.picnic.errorprone.refaster.matchers.IsEmpty;
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;
@@ -380,23 +380,30 @@ final class ReactorRules {
}
/**
* Prefer {@link Flux#take(long)} over {@link Flux#take(long, boolean)} where relevant.
* Prefer {@link Flux#take(long, boolean)} over {@link Flux#take(long)}.
*
* <p>In Reactor versions prior to 3.5.0, {@code Flux#take(long)} makes an unbounded request
* upstream, and is equivalent to {@code Flux#take(long, false)}. From version 3.5.0 onwards, the
* behavior of {@code Flux#take(long)} instead matches {@code Flux#take(long, true)}.
* upstream, and is equivalent to {@code Flux#take(long, false)}. In 3.5.0, the behavior of {@code
* Flux#take(long)} will change to that of {@code Flux#take(long, true)}.
*
* <p>The intent with this Refaster rule is to get the new behavior before upgrading to Reactor
* 3.5.0.
*/
// XXX: Drop this rule some time after upgrading to Reactor 3.6.0, or introduce a way to apply
// this rule only when an older version of Reactor is on the classpath.
// XXX: Once Reactor 3.6.0 is out, introduce a rule that rewrites code in the opposite direction.
@Description(
"From Reactor 3.5.0 onwards, `take(n)` no longer requests an unbounded number of elements upstream.")
"Prior to Reactor 3.5.0, `take(n)` requests and unbounded number of elements upstream.")
@Severity(WARNING)
static final class FluxTake<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, long n) {
return flux.take(n, /* limitRequest= */ true);
return flux.take(n);
}
@AfterTemplate
Flux<T> after(Flux<T> flux, long n) {
return flux.take(n);
return flux.take(n, /* limitRequest= */ true);
}
}
@@ -482,20 +489,15 @@ final class ReactorRules {
}
/** Prefer {@link Flux#just(Object)} over more contrived alternatives. */
static final class FluxJust<T> {
static final class FluxJust {
@BeforeTemplate
Flux<Integer> before(int value) {
return Flux.range(value, 1);
}
@BeforeTemplate
Flux<T> before(T value) {
return Mono.just(value).repeat().take(1);
Flux<Integer> before(int start) {
return Flux.range(start, 1);
}
@AfterTemplate
Flux<T> after(T value) {
return Flux.just(value);
Flux<Integer> after(int start) {
return Flux.just(start);
}
}
@@ -564,7 +566,6 @@ final class ReactorRules {
@Matches(IsIdentityOperation.class)
Function<? super P, ? extends Publisher<? extends S>> identityOperation) {
return Refaster.anyOf(
flux.concatMap(function, 0),
flux.flatMap(function, 1),
flux.flatMapSequential(function, 1),
flux.map(function).concatMap(identityOperation));
@@ -1205,17 +1206,10 @@ final class ReactorRules {
}
/** Prefer {@link Flux#fromIterable(Iterable)} over less efficient alternatives. */
// XXX: Once the `FluxFromStreamSupplier` rule is constrained using
// `@NotMatches(IsIdentityOperation.class)`, this rule should also cover
// `Flux.fromStream(collection.stream())`.
static final class FluxFromIterable<T> {
// XXX: Once the `MethodReferenceUsage` check is generally enabled, drop the second
// `Refaster.anyOf` variant.
@BeforeTemplate
Flux<T> before(Collection<T> collection) {
return Flux.fromStream(
Refaster.<Supplier<Stream<? extends T>>>anyOf(
collection::stream, () -> collection.stream()));
return Flux.fromStream(collection.stream());
}
@AfterTemplate
@@ -1918,60 +1912,4 @@ final class ReactorRules {
return step.verifyTimeout(duration);
}
}
/**
* Prefer {@link Mono#fromFuture(Supplier)} over {@link Mono#fromFuture(CompletableFuture)}, as
* the former may defer initiation of the asynchronous computation until subscription.
*/
static final class MonoFromFutureSupplier<T> {
// XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once
// `IsIdentityOperation` no longer matches nullary method invocations.
@BeforeTemplate
Mono<T> before(CompletableFuture<T> future) {
return Mono.fromFuture(future);
}
@AfterTemplate
Mono<T> after(CompletableFuture<T> future) {
return Mono.fromFuture(() -> future);
}
}
/**
* Prefer {@link Mono#fromFuture(Supplier, boolean)} over {@link
* Mono#fromFuture(CompletableFuture, boolean)}, as the former may defer initiation of the
* asynchronous computation until subscription.
*/
static final class MonoFromFutureSupplierBoolean<T> {
// XXX: Constrain the `future` parameter using `@NotMatches(IsIdentityOperation.class)` once
// `IsIdentityOperation` no longer matches nullary method invocations.
@BeforeTemplate
Mono<T> before(CompletableFuture<T> future, boolean suppressCancel) {
return Mono.fromFuture(future, suppressCancel);
}
@AfterTemplate
Mono<T> after(CompletableFuture<T> future, boolean suppressCancel) {
return Mono.fromFuture(() -> future, suppressCancel);
}
}
/**
* Prefer {@link Flux#fromStream(Supplier)} over {@link Flux#fromStream(Stream)}, as the former
* yields a {@link Flux} that is more likely to behave as expected when subscribed to more than
* once.
*/
static final class FluxFromStreamSupplier<T> {
// XXX: Constrain the `stream` parameter using `@NotMatches(IsIdentityOperation.class)` once
// `IsIdentityOperation` no longer matches nullary method invocations.
@BeforeTemplate
Flux<T> before(Stream<T> stream) {
return Flux.fromStream(stream);
}
@AfterTemplate
Flux<T> after(Stream<T> stream) {
return Flux.fromStream(() -> stream);
}
}
}

View File

@@ -161,9 +161,8 @@ final class TestNGToAssertJRules {
@AfterTemplate
@DoNotCall
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after() {
fail();
throw new AssertionError();
}
}

View File

@@ -1,34 +1,26 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.util.function.Function;
import java.io.IOException;
import java.util.function.Predicate;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ClassRulesTest implements RefasterRuleCollectionTestCase {
boolean testClassIsInstance() {
boolean testClassIsInstance() throws IOException {
return CharSequence.class.isAssignableFrom("foo".getClass());
}
ImmutableSet<Boolean> testInstanceof() {
ImmutableSet<Boolean> testInstanceof() throws IOException {
Class<?> clazz = CharSequence.class;
return ImmutableSet.of(CharSequence.class.isInstance("foo"), clazz.isInstance("bar"));
}
Predicate<String> testClassLiteralIsInstancePredicate() {
Predicate<String> testClassLiteralIsInstancePredicate() throws IOException {
return s -> s instanceof CharSequence;
}
Predicate<String> testClassReferenceIsInstancePredicate() {
Predicate<String> testClassReferenceIsInstancePredicate() throws IOException {
Class<?> clazz = CharSequence.class;
return s -> clazz.isInstance(s);
}
Function<Number, Integer> testClassLiteralCast() {
return i -> (Integer) i;
}
Function<Number, Integer> testClassReferenceCast() {
return i -> Integer.class.cast(i);
}
}

View File

@@ -1,34 +1,26 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.util.function.Function;
import java.io.IOException;
import java.util.function.Predicate;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ClassRulesTest implements RefasterRuleCollectionTestCase {
boolean testClassIsInstance() {
boolean testClassIsInstance() throws IOException {
return CharSequence.class.isInstance("foo");
}
ImmutableSet<Boolean> testInstanceof() {
ImmutableSet<Boolean> testInstanceof() throws IOException {
Class<?> clazz = CharSequence.class;
return ImmutableSet.of("foo" instanceof CharSequence, clazz.isInstance("bar"));
}
Predicate<String> testClassLiteralIsInstancePredicate() {
Predicate<String> testClassLiteralIsInstancePredicate() throws IOException {
return CharSequence.class::isInstance;
}
Predicate<String> testClassReferenceIsInstancePredicate() {
Predicate<String> testClassReferenceIsInstancePredicate() throws IOException {
Class<?> clazz = CharSequence.class;
return clazz::isInstance;
}
Function<Number, Integer> testClassLiteralCast() {
return Integer.class::cast;
}
Function<Number, Integer> testClassReferenceCast() {
return Integer.class::cast;
}
}

View File

@@ -6,11 +6,9 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.TreeSet;
import java.util.stream.Stream;
@@ -72,10 +70,6 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
}
}
Stream<Integer> testSetStream() {
return ImmutableSet.of(1).stream().distinct();
}
ArrayList<String> testNewArrayListFromCollection() {
return Lists.newArrayList(ImmutableList.of("foo"));
}
@@ -100,10 +94,6 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(1).asList().toString();
}
List<String> testArraysAsList() {
return Arrays.stream(new String[0]).toList();
}
ImmutableSet<Object[]> testCollectionToArray() {
return ImmutableSet.of(
ImmutableSet.of(1).toArray(new Object[1]),

View File

@@ -6,11 +6,9 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.TreeSet;
import java.util.stream.Stream;
@@ -64,10 +62,6 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
new HashSet<Number>().removeAll(ImmutableSet.of(2));
}
Stream<Integer> testSetStream() {
return ImmutableSet.of(1).stream();
}
ArrayList<String> testNewArrayListFromCollection() {
return new ArrayList<>(ImmutableList.of("foo"));
}
@@ -92,10 +86,6 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(1).toString();
}
List<String> testArraysAsList() {
return Arrays.asList(new String[0]);
}
ImmutableSet<Object[]> testCollectionToArray() {
return ImmutableSet.of(
ImmutableSet.of(1).toArray(), ImmutableSet.of(2).toArray(), ImmutableSet.of(3).toArray());

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.function.Predicate.isEqual;
import static java.util.function.Predicate.not;
import com.google.common.collect.BoundType;
@@ -15,10 +14,10 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Optional.class, isEqual(null), not(null));
return ImmutableSet.of(Objects.class, Optional.class, not(null));
}
ImmutableSet<Boolean> testEnumReferenceEquality() {
ImmutableSet<Boolean> testPrimitiveOrReferenceEquality() {
return ImmutableSet.of(
RoundingMode.UP.equals(RoundingMode.DOWN),
Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
@@ -28,10 +27,6 @@ final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
RoundingMode.UP.ordinal() != RoundingMode.DOWN.ordinal());
}
ImmutableSet<Predicate<RoundingMode>> testEnumReferenceEqualityLambda() {
return ImmutableSet.of(isEqual(RoundingMode.DOWN), RoundingMode.UP::equals);
}
boolean testEqualsPredicate() {
// XXX: When boxing is involved this rule seems to break. Example:
// Stream.of(1).anyMatch(e -> Integer.MIN_VALUE.equals(e));

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.function.Predicate.isEqual;
import static java.util.function.Predicate.not;
import com.google.common.collect.BoundType;
@@ -15,10 +14,10 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Optional.class, isEqual(null), not(null));
return ImmutableSet.of(Objects.class, Optional.class, not(null));
}
ImmutableSet<Boolean> testEnumReferenceEquality() {
ImmutableSet<Boolean> testPrimitiveOrReferenceEquality() {
return ImmutableSet.of(
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
@@ -28,10 +27,6 @@ final class EqualityRulesTest implements RefasterRuleCollectionTestCase {
RoundingMode.UP != RoundingMode.DOWN);
}
ImmutableSet<Predicate<RoundingMode>> testEnumReferenceEqualityLambda() {
return ImmutableSet.of(v -> v == RoundingMode.DOWN, v -> v == RoundingMode.UP);
}
boolean testEqualsPredicate() {
// XXX: When boxing is involved this rule seems to break. Example:
// Stream.of(1).anyMatch(e -> Integer.MIN_VALUE.equals(e));

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -16,9 +14,4 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
String testFilesReadString() throws IOException {
return Files.readString(Paths.get("foo"), StandardCharsets.UTF_8);
}
ImmutableSet<File> testFilesCreateTempFileToFile() throws IOException {
return ImmutableSet.of(
File.createTempFile("foo", "bar"), File.createTempFile("baz", "qux", null));
}
}

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -16,9 +14,4 @@ final class FileRulesTest implements RefasterRuleCollectionTestCase {
String testFilesReadString() throws IOException {
return Files.readString(Paths.get("foo"));
}
ImmutableSet<File> testFilesCreateTempFileToFile() throws IOException {
return ImmutableSet.of(
Files.createTempFile("foo", "bar").toFile(), Files.createTempFile("baz", "qux").toFile());
}
}

View File

@@ -32,8 +32,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
(Runnable) () -> assertTrue(true));
}
Object testFail() {
return Assertions.fail();
void testThrowNewAssertionError() {
Assertions.fail();
}
Object testFailWithMessage() {
@@ -44,8 +44,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
return Assertions.fail("foo", new IllegalStateException());
}
Object testFailWithThrowable() {
return Assertions.fail(new IllegalStateException());
void testFailWithThrowable() {
Assertions.fail(new IllegalStateException());
}
void testAssertThatIsTrue() {

View File

@@ -35,8 +35,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
(Runnable) () -> assertTrue(true));
}
Object testFail() {
return org.assertj.core.api.Assertions.fail();
void testThrowNewAssertionError() {
throw new AssertionError();
}
Object testFailWithMessage() {
@@ -47,8 +47,8 @@ final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
return org.assertj.core.api.Assertions.fail("foo", new IllegalStateException());
}
Object testFailWithThrowable() {
return org.assertj.core.api.Assertions.fail(new IllegalStateException());
void testFailWithThrowable() {
throw new AssertionError(new IllegalStateException());
}
void testAssertThatIsTrue() {

View File

@@ -13,10 +13,6 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(Streams.class);
}
Optional<String> testOptionalEmpty() {
return Optional.ofNullable(null);
}
ImmutableSet<Optional<String>> testOptionalOfNullable() {
return ImmutableSet.of(
toString() == null ? Optional.empty() : Optional.of(toString()),

View File

@@ -15,10 +15,6 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(Streams.class);
}
Optional<String> testOptionalEmpty() {
return Optional.empty();
}
ImmutableSet<Optional<String>> testOptionalOfNullable() {
return ImmutableSet.of(Optional.ofNullable(toString()), Optional.ofNullable(toString()));
}

View File

@@ -21,9 +21,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.math.MathFlux;
@@ -144,7 +142,7 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
Flux<Integer> testFluxTake() {
return Flux.just(1, 2, 3).take(1, true);
return Flux.just(1, 2, 3).take(1);
}
Mono<String> testMonoDefaultIfEmpty() {
@@ -184,8 +182,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.range(0, 0));
}
ImmutableSet<Flux<Integer>> testFluxJust() {
return ImmutableSet.of(Flux.range(0, 1), Mono.just(2).repeat().take(1));
Flux<Integer> testFluxJust() {
return Flux.range(0, 1);
}
ImmutableSet<Mono<?>> testMonoIdentity() {
@@ -209,12 +207,11 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Flux<Integer>> testFluxConcatMap() {
return ImmutableSet.of(
Flux.just(1).concatMap(Mono::just, 0),
Flux.just(2).flatMap(Mono::just, 1),
Flux.just(3).flatMapSequential(Mono::just, 1),
Flux.just(4).map(Mono::just).concatMap(identity()),
Flux.just(5).map(Mono::just).concatMap(v -> v),
Flux.just(6).map(Mono::just).concatMap(v -> Mono.empty()));
Flux.just(1).flatMap(Mono::just, 1),
Flux.just(2).flatMapSequential(Mono::just, 1),
Flux.just(3).map(Mono::just).concatMap(identity()),
Flux.just(4).map(Mono::just).concatMap(v -> v),
Flux.just(5).map(Mono::just).concatMap(v -> Mono.empty()));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
@@ -435,10 +432,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2));
}
ImmutableSet<Flux<String>> testFluxFromIterable() {
return ImmutableSet.of(
Flux.fromStream(ImmutableList.of("foo")::stream),
Flux.fromStream(() -> ImmutableList.of("bar").stream()));
Flux<String> testFluxFromIterable() {
return Flux.fromStream(ImmutableList.of("foo").stream());
}
ImmutableSet<Mono<Integer>> testFluxCountMapMathToIntExact() {
@@ -655,16 +650,4 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Duration testStepVerifierLastStepVerifyTimeout() {
return Mono.empty().as(StepVerifier::create).expectTimeout(Duration.ZERO).verify();
}
Mono<Void> testMonoFromFutureSupplier() {
return Mono.fromFuture(CompletableFuture.completedFuture(null));
}
Mono<Void> testMonoFromFutureSupplierBoolean() {
return Mono.fromFuture(CompletableFuture.completedFuture(null), true);
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(Stream.of(1));
}
}

View File

@@ -23,9 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.function.TupleUtils;
@@ -149,7 +147,7 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
}
Flux<Integer> testFluxTake() {
return Flux.just(1, 2, 3).take(1);
return Flux.just(1, 2, 3).take(1, true);
}
Mono<String> testMonoDefaultIfEmpty() {
@@ -188,8 +186,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.empty());
}
ImmutableSet<Flux<Integer>> testFluxJust() {
return ImmutableSet.of(Flux.just(0), Flux.just(2));
Flux<Integer> testFluxJust() {
return Flux.just(0);
}
ImmutableSet<Mono<?>> testMonoIdentity() {
@@ -216,8 +214,7 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(2).concatMap(Mono::just),
Flux.just(3).concatMap(Mono::just),
Flux.just(4).concatMap(Mono::just),
Flux.just(5).concatMap(Mono::just),
Flux.just(6).map(Mono::just).concatMap(v -> Mono.empty()));
Flux.just(5).map(Mono::just).concatMap(v -> Mono.empty()));
}
ImmutableSet<Flux<Integer>> testFluxConcatMapWithPrefetch() {
@@ -430,9 +427,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2));
}
ImmutableSet<Flux<String>> testFluxFromIterable() {
return ImmutableSet.of(
Flux.fromIterable(ImmutableList.of("foo")), Flux.fromIterable(ImmutableList.of("bar")));
Flux<String> testFluxFromIterable() {
return Flux.fromIterable(ImmutableList.of("foo"));
}
ImmutableSet<Mono<Integer>> testFluxCountMapMathToIntExact() {
@@ -635,16 +631,4 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Duration testStepVerifierLastStepVerifyTimeout() {
return Mono.empty().as(StepVerifier::create).verifyTimeout(Duration.ZERO);
}
Mono<Void> testMonoFromFutureSupplier() {
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null));
}
Mono<Void> testMonoFromFutureSupplierBoolean() {
return Mono.fromFuture(() -> CompletableFuture.completedFuture(null), true);
}
Flux<Integer> testFluxFromStreamSupplier() {
return Flux.fromStream(() -> Stream.of(1));
}
}

View File

@@ -41,7 +41,7 @@ final class TestNGToAssertJRulesTest implements RefasterRuleCollectionTestCase {
}
void testFail() {
fail();
throw new AssertionError();
}
void testFailWithMessage() {

View File

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

View File

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

View File

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

View File

@@ -2,35 +2,211 @@
set -e -u -o pipefail
integration_test_root="$(cd "$(dirname -- "${0}")" && pwd)"
error_prone_support_root="${integration_test_root}/.."
repos_root="${integration_test_root}/.repos"
test_name="$(basename "${0}" .sh)"
project='checkstyle'
repository='https://github.com/checkstyle/checkstyle.git'
revision='checkstyle-10.14.0'
# XXX: Configure Renovate to manage the AssertJ version declared here.
additional_build_flags='-Dassertj.version=3.24.2'
additional_source_directories='${project.basedir}${file.separator}src${file.separator}it${file.separator}java,${project.basedir}${file.separator}src${file.separator}xdocs-examples${file.separator}java'
patch_error_prone_flags=''
validation_error_prone_flags=''
# Validation skips some tests:
# - The `metadataFilesGenerationAllFiles` test is skipped because it makes line
# number assertions that will fail when the code is formatted or patched.
# - The `allCheckSectionJavaDocs` test is skipped because it validates that
# Javadoc has certain closing tags that are removed by Google Java Format.
validation_build_flags='-Dtest=!MetadataGeneratorUtilTest#metadataFilesGenerationAllFiles,!XdocsJavaDocsTest#allCheckSectionJavaDocs'
project=checkstyle
repository=https://github.com/checkstyle/checkstyle.git
revision=checkstyle-10.14.0
if [ "${#}" -gt 2 ] || ([ "${#}" = 2 ] && [ "${1:---sync}" != '--sync' ]); then
>&2 echo "Usage: ${0} [--sync] [<report_directory>]"
echo "Usage: ${0} [--sync] [<report_directory>]"
exit 1
fi
do_sync="$([ "${#}" = 0 ] || [ "${1:-}" != '--sync' ] || echo 1)"
report_directory="$([ "${#}" = 0 ] || ([ -z "${do_sync}" ] && echo "${1}") || ([ "${#}" = 1 ] || echo "${2}"))"
"$(dirname "${0}")/run-integration-test.sh" \
"${test_name}" \
"${project}" \
"${repository}" \
"${revision}" \
"${additional_build_flags}" \
"${additional_source_directories}" \
"${patch_error_prone_flags}" \
"${validation_error_prone_flags}" \
"${validation_build_flags}" \
$@
if [ -n "${report_directory}" ]; then
mkdir -p "${report_directory}"
else
report_directory="$(mktemp -d)"
trap 'rm -rf -- "${report_directory}"' INT TERM HUP EXIT
fi
case "$(uname -s)" in
Linux*)
grep_command=grep
sed_command=sed
;;
Darwin*)
grep_command=ggrep
sed_command=gsed
;;
*)
echo "Unsupported distribution $(uname -s) for this script."
exit 1
;;
esac
# XXX: Configure Renovate to manage the AssertJ version declared here.
shared_build_flags="
-Perror-prone-compile,error-prone-test-compile
-Dassertj.version=3.24.2
-Derror-prone.version=$(
mvn -f "${error_prone_support_root}" help:evaluate -Dexpression=version.error-prone -q -DforceStdout
)
-Derror-prone-support.version=$(
mvn -f "${error_prone_support_root}" help:evaluate -Dexpression=project.version -q -DforceStdout
)
-DadditionalSourceDirectories=\${project.basedir}\${file.separator}src\${file.separator}it\${file.separator}java,\${project.basedir}\${file.separator}src\${file.separator}xdocs-examples\${file.separator}java
"
# XXX: Configure Renovate to manage the fmt-maven-plugin version declared here.
# XXX: Once GitHub actions uses Maven 3.9.2+, we can inline this variable with
# version reference `${fmt.version}`, and `-Dfmt.version=2.21.1` added to
# `shared_build_flags`.
format_goal='com.spotify.fmt:fmt-maven-plugin:2.21.1:format'
error_prone_shared_flags='-XepExcludedPaths:(\Q${project.basedir}${file.separator}src${file.separator}\E(it|test|xdocs-examples)\Q${file.separator}resources\E|\Q${project.build.directory}${file.separator}\E).*'
error_prone_patch_flags="${error_prone_shared_flags} -XepPatchLocation:IN_PLACE -XepPatchChecks:$(
find "${error_prone_support_root}" \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-not -path "*/error-prone-contrib/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP '[^.]+$' \
| paste -s -d ',' -
) -XepOpt:Refaster:NamePattern=.*Workshop.*"
error_prone_validation_flags="${error_prone_shared_flags} -XepDisableAllChecks $(
find "${error_prone_support_root}" \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-not -path "*/error-prone-contrib/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP "[^.]+$' \
| "${sed_command}" -r 's,(.*),-Xep:\1:WARN,' \
| paste -s -d ' ' -
) -XepOpt:Refaster:NamePattern=.*Workshop.*"
echo "Shared build flags: ${shared_build_flags}"
echo "Error Prone patch flags: ${error_prone_patch_flags}"
echo "Error Prone validation flags: ${error_prone_validation_flags}"
mkdir -p "${repos_root}"
# Make sure that the targeted tag of the project's Git repository is checked
# out.
project_root="${repos_root}/${project}"
if [ ! -d "${project_root}" ]; then
# The repository has not yet been cloned; create a shallow clone.
git clone --branch "${revision}" --depth 1 "${repository}" "${project_root}"
else
# The repository does already appear to exist. Try to check out the requested
# tag if possible, and fetch it otherwise.
#
# Under certain circumstances this does not cause the relevant tag to be
# created, so if necessary we manually create it.
git -C "${project_root}" checkout --force "${revision}" 2>/dev/null \
|| (
git -C "${project_root}" fetch --depth 1 "${repository}" "${revision}" \
&& git -C "${project_root}" checkout --force FETCH_HEAD \
&& (git -C "${project_root}" tag "${revision}" || true)
)
fi
pushd "${project_root}"
# Make sure that Git is sufficiently configured to enable committing to the
# project's Git repository.
git config user.email || git config user.email "integration-test@example.com"
git config user.name || git config user.name "Integration Test"
# Prepare the code for analysis by (a) applying the minimal set of changes
# required to run Error Prone with Error Prone Support and (b) formatting the
# code using the same method by which it will be formatted after each
# compilation round. The initial formatting operation ensures that subsequent
# modifications can be rendered in a clean manner.
git clean -fdx
git apply < "${integration_test_root}/${test_name}-init.patch"
git commit -m 'dependency: Introduce Error Prone Support' .
mvn ${shared_build_flags} "${format_goal}"
git commit -m 'minor: Reformat using Google Java Format' .
diff_base="$(git rev-parse HEAD)"
# Apply Error Prone Support-suggested changes until a fixed point is reached.
function apply_patch() {
local extra_build_args="${1}"
mvn ${shared_build_flags} ${extra_build_args} \
package "${format_goal}" \
-Derror-prone.configuration-args="${error_prone_patch_flags}" \
-DskipTests
if ! git diff --exit-code; then
git commit -m 'minor: Apply patches' .
# Changes were applied, so another compilation round may apply yet more
# changes. For performance reasons we perform incremental compilation,
# enabled using a misleading flag. (See
# https://issues.apache.org/jira/browse/MCOMPILER-209 for details.)
apply_patch '-Dmaven.compiler.useIncrementalCompilation=false'
elif [ "${extra_build_args}" != 'clean' ]; then
# No changes were applied. We'll attempt one more round in which all files
# are recompiled, because there are cases in which violations are missed
# during incremental compilation.
apply_patch 'clean'
fi
}
apply_patch ''
# Run one more full build and log the output.
#
# By also running the tests, we validate that the (majority of) applied changes
# are behavior preserving. Some tests are skipped:
# - The `metadataFilesGenerationAllFiles` test is skipped because it makes line
# number assertions that will fail when the code is formatted or patched.
# - The `allCheckSectionJavaDocs` test is skipped because is validates that
# Javadoc has certain closing tags that are removed by Google Java Format.
validation_build_log="${report_directory}/${test_name}-validation-build-log.txt"
mvn ${shared_build_flags} \
clean package \
-Derror-prone.configuration-args="${error_prone_validation_flags}" \
-Dtest='
!MetadataGeneratorUtilTest#metadataFilesGenerationAllFiles,
!XdocsJavaDocsTest#allCheckSectionJavaDocs' \
-Dstyle.color=always \
| tee "${validation_build_log}" \
|| failure=1
# Collect the applied changes.
expected_changes="${integration_test_root}/${test_name}-expected-changes.patch"
actual_changes="${report_directory}/${test_name}-changes.patch"
(git diff "${diff_base}"..HEAD | "${grep_command}" -vP '^(diff|index)' || true) > "${actual_changes}"
# Collect the warnings reported by Error Prone Support checks.
expected_warnings="${integration_test_root}/${test_name}-expected-warnings.txt"
actual_warnings="${report_directory}/${test_name}-validation-build-warnings.txt"
("${grep_command}" -oP "(?<=^\\Q[WARNING] ${PWD}/\\E).*" "${validation_build_log}" | "${grep_command}" -P '\] \[' || true) | LC_ALL=C sort > "${actual_warnings}"
# Persist or validate the applied changes and reported warnings.
if [ -n "${do_sync}" ]; then
echo 'Saving changes...'
cp "${actual_changes}" "${expected_changes}"
cp "${actual_warnings}" "${expected_warnings}"
else
echo 'Inspecting changes...'
# XXX: This "diff of diffs" also contains vacuous sections, introduced due to
# line offset differences. Try to omit those from the final output.
if ! diff -u "${expected_changes}" "${actual_changes}"; then
echo 'There are unexpected changes.'
echo "Inspect the changes here: ${report_directory}/${test_name}-diff-of-diffs-changes.patch"
diff -u "${expected_changes}" "${actual_changes}" > "${report_directory}/${test_name}-diff-of-diffs-changes.patch"
failure=1
fi
echo 'Inspecting emitted warnings...'
if ! diff -u "${expected_warnings}" "${actual_warnings}"; then
echo 'Diagnostics output changed.'
diff -u "${expected_warnings}" "${actual_warnings}" > "${report_directory}/${test_name}-diff-of-diffs-warnings.patch"
failure=1
fi
fi
if [ -n "${failure:-}" ]; then
exit 1
fi

View File

@@ -1,210 +0,0 @@
#!/usr/bin/env bash
# Integration test framework for Maven builds.
#
# This script is not meant to be invoked manually. Instead it should be invoked
# through one of the top-level integration test scripts, such as
# `checkstyle.sh`.
set -e -u -o pipefail
integration_test_root="$(cd "$(dirname -- "${0}")" && pwd)"
error_prone_support_root="${integration_test_root}/.."
repos_root="${integration_test_root}/.repos"
if [ "${#}" -lt 9 ] || [ "${#}" -gt 11 ] || ([ "${#}" = 11 ] && [ "${10:---sync}" != '--sync' ]); then
>&2 echo "Usage: $(basename "${0}") <test_name> <project> <repository> <revision> <additional_build_flags> <additional_source_directories> <patch_error_prone_flags> <validation_error_prone_flags> <validation_build_flags> [--sync] [<report_directory>]"
exit 1
fi
test_name="${1}"
project="${2}"
repository="${3}"
revision="${4}"
additional_build_flags="${5}"
additional_source_directories="${6}"
patch_error_prone_flags="${7}"
validation_error_prone_flags="${8}"
validation_build_flags="${9}"
do_sync="$([ "${#}" = 9 ] || [ "${10:-}" != '--sync' ] || echo 1)"
report_directory="$([ "${#}" = 9 ] || ([ -z "${do_sync}" ] && echo "${10}") || ([ "${#}" = 10 ] || echo "${11}"))"
if [ -n "${report_directory}" ]; then
mkdir -p "${report_directory}"
else
report_directory="$(mktemp -d)"
trap 'rm -rf -- "${report_directory}"' INT TERM HUP EXIT
fi
case "$(uname -s)" in
Linux*)
grep_command=grep
sed_command=sed
;;
Darwin*)
grep_command=ggrep
sed_command=gsed
;;
*)
echo "Unsupported distribution $(uname -s) for this script."
exit 1
;;
esac
shared_build_flags="
-Perror-prone-compile,error-prone-test-compile
-Derror-prone.version=$(
mvn -f "${error_prone_support_root}" help:evaluate -Dexpression=version.error-prone -q -DforceStdout
)
-Derror-prone-support.version=$(
mvn -f "${error_prone_support_root}" help:evaluate -Dexpression=project.version -q -DforceStdout
)
-DadditionalSourceDirectories=${additional_source_directories}
${additional_build_flags}
"
# XXX: Configure Renovate to manage the fmt-maven-plugin version declared here.
# XXX: Once GitHub actions uses Maven 3.9.2+, we can inline this variable with
# version reference `${fmt.version}`, and `-Dfmt.version=2.21.1` added to
# `shared_build_flags`.
format_goal='com.spotify.fmt:fmt-maven-plugin:2.21.1:format'
error_prone_shared_flags='-XepExcludedPaths:(\Q${project.basedir}${file.separator}src${file.separator}\E(it|test|xdocs-examples)\Q${file.separator}resources\E|\Q${project.build.directory}${file.separator}\E).*'
error_prone_patch_flags="${error_prone_shared_flags} -XepPatchLocation:IN_PLACE -XepPatchChecks:$(
find "${error_prone_support_root}" \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP '[^.]+$' \
| paste -s -d ',' -
) ${patch_error_prone_flags}"
error_prone_validation_flags="${error_prone_shared_flags} -XepDisableAllChecks $(
find "${error_prone_support_root}" \
-path "*/META-INF/services/com.google.errorprone.bugpatterns.BugChecker" \
-not -path "*/error-prone-experimental/*" \
-not -path "*/error-prone-guidelines/*" \
-print0 \
| xargs -0 "${grep_command}" -hoP '[^.]+$' \
| "${sed_command}" -r 's,(.*),-Xep:\1:WARN,' \
| paste -s -d ' ' -
) ${validation_error_prone_flags}"
echo "Shared build flags: ${shared_build_flags}"
echo "Error Prone patch flags: ${error_prone_patch_flags}"
echo "Error Prone validation flags: ${error_prone_validation_flags}"
mkdir -p "${repos_root}"
# Make sure that the targeted tag of the project's Git repository is checked
# out.
project_root="${repos_root}/${project}"
if [ ! -d "${project_root}" ]; then
# The repository has not yet been cloned; create a shallow clone.
git clone --branch "${revision}" --depth 1 "${repository}" "${project_root}"
else
# The repository does already appear to exist. Try to check out the requested
# tag if possible, and fetch it otherwise.
#
# Under certain circumstances this does not cause the relevant tag to be
# created, so if necessary we manually create it.
git -C "${project_root}" checkout --force "${revision}" 2>/dev/null \
|| (
git -C "${project_root}" fetch --depth 1 "${repository}" "${revision}" \
&& git -C "${project_root}" checkout --force FETCH_HEAD \
&& (git -C "${project_root}" tag "${revision}" || true)
)
fi
pushd "${project_root}"
# Make sure that Git is sufficiently configured to enable committing to the
# project's Git repository.
git config user.email || git config user.email 'integration-test@example.com'
git config user.name || git config user.name 'Integration Test'
# Prepare the code for analysis by (a) applying the minimal set of changes
# required to run Error Prone with Error Prone Support and (b) formatting the
# code using the same method by which it will be formatted after each
# compilation round. The initial formatting operation ensures that subsequent
# modifications can be rendered in a clean manner.
git clean -fdx
git apply < "${integration_test_root}/${test_name}-init.patch"
git commit -m 'dependency: Introduce Error Prone Support' .
mvn ${shared_build_flags} "${format_goal}"
git commit -m 'minor: Reformat using Google Java Format' .
diff_base="$(git rev-parse HEAD)"
# Apply Error Prone Support-suggested changes until a fixed point is reached.
function apply_patch() {
local extra_build_args="${1}"
mvn ${shared_build_flags} ${extra_build_args} \
package "${format_goal}" \
-Derror-prone.configuration-args="${error_prone_patch_flags}" \
-DskipTests
if ! git diff --exit-code; then
git commit -m 'minor: Apply patches' .
# Changes were applied, so another compilation round may apply yet more
# changes. For performance reasons we perform incremental compilation,
# enabled using a misleading flag. (See
# https://issues.apache.org/jira/browse/MCOMPILER-209 for details.)
apply_patch '-Dmaven.compiler.useIncrementalCompilation=false'
elif [ "${extra_build_args}" != 'clean' ]; then
# No changes were applied. We'll attempt one more round in which all files
# are recompiled, because there are cases in which violations are missed
# during incremental compilation.
apply_patch 'clean'
fi
}
apply_patch ''
# Run one more full build and log the output.
#
# By also running the tests, we validate that the (majority of) applied changes
# are behavior preserving.
validation_build_log="${report_directory}/${test_name}-validation-build-log.txt"
mvn ${shared_build_flags} \
clean package \
-Derror-prone.configuration-args="${error_prone_validation_flags}" \
${validation_build_flags} \
| tee "${validation_build_log}" \
|| failure=1
# Collect the applied changes.
expected_changes="${integration_test_root}/${test_name}-expected-changes.patch"
actual_changes="${report_directory}/${test_name}-changes.patch"
(git diff "${diff_base}"..HEAD | "${grep_command}" -vP '^(diff|index)' || true) > "${actual_changes}"
# Collect the warnings reported by Error Prone Support checks.
expected_warnings="${integration_test_root}/${test_name}-expected-warnings.txt"
actual_warnings="${report_directory}/${test_name}-validation-build-warnings.txt"
("${grep_command}" -oP "(?<=^\\Q[WARNING] ${PWD}/\\E).*" "${validation_build_log}" | "${grep_command}" -P '\] \[' || true) | LC_ALL=C sort > "${actual_warnings}"
# Persist or validate the applied changes and reported warnings.
if [ -n "${do_sync}" ]; then
echo 'Saving changes...'
cp "${actual_changes}" "${expected_changes}"
cp "${actual_warnings}" "${expected_warnings}"
else
echo 'Inspecting changes...'
# XXX: This "diff of diffs" also contains vacuous sections, introduced due to
# line offset differences. Try to omit those from the final output.
if ! diff -u "${expected_changes}" "${actual_changes}"; then
echo 'There are unexpected changes. Inspect the preceding output for details.'
failure=1
fi
echo 'Inspecting emitted warnings...'
if ! diff -u "${expected_warnings}" "${actual_warnings}"; then
echo 'Diagnostics output changed. Inspect the preceding output for details.'
failure=1
fi
fi
if [ -n "${failure:-}" ]; then
exit 1
fi

191
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.16.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -48,11 +48,12 @@
<module>refaster-runner</module>
<module>refaster-support</module>
<module>refaster-test-support</module>
<module>workshop</module>
</modules>
<scm child.scm.developerConnection.inherit.append.path="false" child.scm.url.inherit.append.path="false">
<developerConnection>scm:git:git@github.com:PicnicSupermarket/error-prone-support.git</developerConnection>
<tag>v0.18.0</tag>
<tag>HEAD</tag>
<url>https://github.com/PicnicSupermarket/error-prone-support</url>
</scm>
<issueManagement>
@@ -148,7 +149,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-03-15T12:04:44Z</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
@@ -205,19 +206,19 @@
one place. We use these to keep dependencies in sync. Version numbers
that need to be referenced only once should *not* be listed here. -->
<version.auto-service>1.1.1</version.auto-service>
<version.auto-value>1.11.0</version.auto-value>
<version.auto-value>1.10.4</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-fork>v${version.error-prone-orig}-picnic-2</version.error-prone-fork>
<version.error-prone-orig>2.26.1</version.error-prone-orig>
<version.error-prone-slf4j>0.1.23</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.mockito>5.11.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.11.1</version.nullaway>
<version.nullaway>0.10.25</version.nullaway>
<version.pitest-git>1.1.4</version.pitest-git>
<version.rewrite-templating>1.12.3</version.rewrite-templating>
<version.rewrite-templating>1.6.3</version.rewrite-templating>
<version.surefire>3.2.3</version.surefire>
</properties>
@@ -296,7 +297,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.17.2</version>
<version>2.17.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -328,7 +329,7 @@
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
<version>1.23.0</version>
<version>1.22.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
@@ -338,14 +339,14 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>33.2.1-jre</version>
<version>33.1.0-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.4.4</version>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.jakewharton.nopen</groupId>
@@ -360,7 +361,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2023.0.8</version>
<version>2023.0.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -377,17 +378,12 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.22</version>
<version>2.2.21</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
@@ -412,37 +408,29 @@
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.14.18</version>
</dependency>
<!-- Specified so that Renovate will file Maven upgrade PRs, which
subsequently will cause `maven-enforcer-plugin` to require that
developers build the project using the latest Maven release. -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>${version.maven}</version>
<version>1.14.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22.1</version>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-bom</artifactId>
<version>3.26.3</version>
<version>3.25.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.46.0</version>
<version>3.42.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>3.0</version>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.immutables</groupId>
@@ -452,12 +440,12 @@
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.3</version>
<version>5.10.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -471,7 +459,7 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>5.1.3</version>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
@@ -481,40 +469,40 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>2.16.0</version>
<version>2.8.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-bom</artifactId>
<version>2.0.16</version>
<version>2.0.12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>6.1.11</version>
<version>6.1.5</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.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId>
<version>6.3.1</version>
<version>6.2.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
<version>7.9.0</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -525,7 +513,7 @@
<plugin>
<groupId>com.github.ekryd.sortpom</groupId>
<artifactId>sortpom-maven-plugin</artifactId>
<version>4.0.0</version>
<version>3.4.1</version>
<configuration>
<createBackupFile>false</createBackupFile>
<encoding>${project.build.sourceEncoding}</encoding>
@@ -629,7 +617,7 @@
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>9.0.1</version>
<version>8.0.2</version>
<configuration>
<injectAllReactorProjects>true</injectAllReactorProjects>
<runOnlyOnce>true</runOnlyOnce>
@@ -648,7 +636,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.4.0</version>
<version>3.3.1</version>
<configuration>
<checkstyleRules>
<!-- We only enable rules that are not enforced by
@@ -684,7 +672,6 @@
<property name="allowNonPrintableEscapes" value="true" />
</module>
<module name="AvoidNoArgumentSuperConstructorCall" />
<module name="ConstructorsDeclarationGrouping" />
<module name="DeclarationOrder">
<!-- We don't enforce sorting fields by
their visibility modifier, for two
@@ -897,7 +884,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.17.0</version>
<version>10.15.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -917,7 +904,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.4.0</version>
<version>3.3.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -965,43 +952,11 @@
https://issues.apache.org/jira/browse/MCOMPILER-209. -->
<useIncrementalCompilation>false</useIncrementalCompilation>
</configuration>
<executions>
<!-- OpenRewrite recipe sources, generated by the
`rewrite-templating` annotation processor and
identified as classes whose name ends in `Recipes`, are
compiled to target Java 8 for compatibility with the
wider OpenRewrite ecosystem. The `maven-jar-plugin` is
configured to package these files into a separate JAR. -->
<execution>
<id>compile-recipes</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<compilerArgs combine.self="override">
<!-- When Java 8 is targeted we can't use
the `add-exports` flag. And since this goal
only recompiles already-compiled code,
static analysis is irrelevant. As such we
clear all compiler arguments, and only
suppress source/target/bootstrap classpath
warnings. -->
<arg>-Xlint:-options</arg>
</compilerArgs>
<source>8</source>
<target>8</target>
<includes>
<include>**/*Recipes.java</include>
</includes>
<outputDirectory>${project.build.directory}/openrewrite-recipes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.7.1</version>
<version>3.6.1</version>
<configuration>
<!-- XXX: Drop `ignoreAllNonTestScoped` once
https://issues.apache.org/jira/browse/MNG-6058 is
@@ -1016,7 +971,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.2</version>
<version>3.1.1</version>
<configuration>
<retryFailedDeploymentCount>3</retryFailedDeploymentCount>
</configuration>
@@ -1024,7 +979,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<version>3.4.1</version>
<configuration>
<fail>false</fail>
<rules>
@@ -1079,12 +1034,6 @@
<requireJavaVersion>
<version>${version.jdk}</version>
</requireJavaVersion>
<requireMatchingCoordinates>
<moduleNameMustMatchArtifactId>true</moduleNameMustMatchArtifactId>
</requireMatchingCoordinates>
<requireMavenVersion>
<version>${version.maven}</version>
</requireMavenVersion>
<requireNoRepositories />
<requirePluginVersions />
<requireUpperBoundDeps />
@@ -1109,7 +1058,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.4</version>
<version>3.2.2</version>
<executions>
<execution>
<id>sign-artifacts</id>
@@ -1122,12 +1071,12 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.2</version>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<version>3.3.0</version>
<configuration>
<skipIfEmpty>true</skipIfEmpty>
<archive>
@@ -1144,39 +1093,6 @@
</archive>
</configuration>
<executions>
<execution>
<id>default-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<!-- These Java 17 bytecode classes are omitted
from the default JAR, as instead we produce a
separate artifact with Java 8 bytecode variants. -->
<excludes>
<excludes>**/*Recipe$*.class</excludes>
<excludes>**/*Recipe.class</excludes>
<excludes>**/*Recipes.class</excludes>
</excludes>
</configuration>
</execution>
<execution>
<!-- Creates a custom JAR with Java 8-compatible
OpenRewrite recipe classes. -->
<id>create-openrewrite-recipes-jar</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classesDirectory>${project.build.directory}/openrewrite-recipes</classesDirectory>
<classifier>recipes</classifier>
<includes>
<includes>**/*Recipe$*.class</includes>
<includes>**/*Recipe.class</includes>
<includes>**/*Recipes.class</includes>
</includes>
</configuration>
</execution>
<execution>
<id>create-test-jar</id>
<goals>
@@ -1188,7 +1104,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.8.0</version>
<version>3.6.3</version>
<configuration>
<additionalJOptions>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</additionalJOption>
@@ -1217,7 +1133,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>3.1.1</version>
<version>3.0.1</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<preparationProfiles>release</preparationProfiles>
@@ -1244,7 +1160,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<version>3.3.0</version>
<executions>
<execution>
<id>generate-source-jar</id>
@@ -1258,7 +1174,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
<version>3.2.5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
@@ -1274,7 +1190,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
@@ -1370,7 +1286,6 @@
<!-- -->
GPL-2.0-with-classpath-exception
| CDDL/GPLv2+CE
| CDDL + GPLv2 with classpath exception
| GNU General Public License, version 2 (GPL2), with the classpath exception
| GNU General Public License, version 2, with the Classpath Exception
| GPL2 w/ CPE
@@ -1405,7 +1320,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tidy-maven-plugin</artifactId>
<version>1.3.0</version>
<version>1.2.0</version>
<executions>
<execution>
<id>check-pom</id>
@@ -1418,7 +1333,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.17.1</version>
<version>2.16.2</version>
<configuration>
<updateBuildOutputTimestampPolicy>never</updateBuildOutputTimestampPolicy>
</configuration>
@@ -1426,7 +1341,7 @@
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.9.0</version>
<version>2.8.0</version>
<configuration>
<exclusionPatterns>
<!-- The plugin suggests replacing usages of
@@ -1487,7 +1402,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.16.1</version>
<version>1.15.8</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
@@ -1541,7 +1456,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>4.0.0.4121</version>
<version>3.11.0.3922</version>
</plugin>
</plugins>
</pluginManagement>

View File

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

View File

@@ -21,6 +21,7 @@ import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
@@ -111,7 +112,7 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
return (sym != null
&& sym.getQualifiedName()
.contentEquals(BeforeTemplate.class.getCanonicalName()))
|| super.visitAnnotation(node, null);
|| super.visitAnnotation(node, unused);
}
@Override
@@ -136,10 +137,10 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
return enclosingPackage == null ? "" : enclosingPackage.toString();
}
private static String toSimpleFlatName(ClassSymbol symbol) {
String flatName = symbol.flatName().toString();
private static CharSequence toSimpleFlatName(ClassSymbol symbol) {
Name flatName = symbol.flatName();
int lastDot = flatName.lastIndexOf((byte) '.');
return lastDot < 0 ? flatName : flatName.substring(lastDot + 1);
return lastDot < 0 ? flatName : flatName.subSequence(lastDot + 1, flatName.length());
}
private static void outputCodeTransformer(CodeTransformer codeTransformer, FileObject target)

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>refaster-runner</artifactId>
@@ -58,11 +58,6 @@
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.18.0</version>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>refaster-test-support</artifactId>
@@ -50,11 +50,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>

View File

@@ -319,7 +319,7 @@ public final class RefasterRuleCollection extends BugChecker implements Compilat
return indexedMatches.subRangeMap(Range.closedOpen(startPosition, endPosition));
}
private static ImmutableListMultimap<Long, String> getUnexpectedMatchesByLineNumber(
private ImmutableListMultimap<Long, String> getUnexpectedMatchesByLineNumber(
ImmutableRangeMap<Integer, String> matches, String ruleUnderTest, VisitorState state) {
LineMap lineMap = state.getPath().getCompilationUnit().getLineMap();
return matches.asMapOfRanges().entrySet().stream()

View File

@@ -3,12 +3,6 @@
releases:
- version: 0.16.1
compatible:
- "2.29.2"
- "2.29.1"
- "2.29.0"
- "2.28.0"
- "2.27.1"
- "2.27.0"
- "2.26.1"
- "2.26.0"
- "2.25.0"
@@ -17,12 +11,6 @@ releases:
- "2.23.0"
- version: 0.16.0
compatible:
- "2.29.2"
- "2.29.1"
- "2.29.0"
- "2.28.0"
- "2.27.1"
- "2.27.0"
- "2.26.1"
- "2.26.0"
- "2.25.0"
@@ -31,12 +19,6 @@ releases:
- "2.23.0"
- version: 0.15.0
compatible:
- "2.29.2"
- "2.29.1"
- "2.29.0"
- "2.28.0"
- "2.27.1"
- "2.27.0"
- "2.26.1"
- "2.26.0"
- "2.25.0"
@@ -45,12 +27,6 @@ releases:
- "2.23.0"
- version: 0.14.0
compatible:
- "2.29.2"
- "2.29.1"
- "2.29.0"
- "2.28.0"
- "2.27.1"
- "2.27.0"
- "2.26.1"
- "2.26.0"
- "2.25.0"

108
workshop/README.md Normal file
View File

@@ -0,0 +1,108 @@
# Error Prone Workshop
XXX: @Rick update the slides.
## Initial setup of the workshop
1. Start by cloning this repository locally from GitHub.
2. Checkout the `workshop` branch.
3. Make sure to run `mvn clean install` in the root of this repository.
4. Open your code editor and familiarize yourself with the project structure.
In IntelliJ IDEA (or your preferred editor), try to run the
`WorkshopRefasterRulesTest` test. You might see the following error:
```
java: exporting a package from system module jdk.compiler is not allowed with --release
```
If this happens, go to _Settings -> Build, Execution, Deployment -> Compiler ->
Java Compiler_ and deselect the option _Use '--release' option for
cross-compilation (Java 9 and later)_.
Now the project is ready for the workshop.
## Part 1: Writing Refaster rules
During this part of the workshop we will implement multiple Refaster rules.
Go to the `workshop` module and open the
`tech.picnic.errorprone.workshop.refasterrules` package. There you can find one
example and 5 different exercises to do. Make sure to check out the
`WorkshopRefasterRulesTest.java` class where you can enable tests. Per
assignment there is a test in this class that one can enable (by dropping the
`@Disabled` annotation) to validate your changes. The goal is to implement or
improve the Refaster rules such that the enabled tests pass.
Tips:
* Go through the exercises in the proposed order.
* The `XXX:` comments explains what needs to happen.
* See the test cases for each Refaster rule by looking for the name of the
Refaster rule prefixed with `test`. For example, the
`WorkshopAssignment0Rules.java` rule collection has a Refaster rule named
`ExampleStringIsEmpty`. In the `WorkshopAssignment0RulesTestInput.java` and
`WorkshopAssignment0RulesTestOutput.java` files there is a
`testExampleStringIsEmpty` method that shows the input and output to test the
Refaster rule.
## Part 2: Writing Error Prone checks
During this part of the workshop we will implement parts of multiple Error
Prone `BugChecker`s. Each of these classes contain `XXX` comments explaining
what needs to be implemented. However, before diving in, make sure to first
navigate to a check's associated test class to drop the class-level `@Disabled`
annotation. Upon initial execution the tests will fail; the goal is to get then
to pass.
Some utility classes that you can use:
* `com.google.errorprone.util.ASTHelpers`: contains many common operations on
the Abstract Syntax Tree.
* `com.google.errorprone.fixes.SuggestedFixes`: contains helper methods for
creating `Fix`es.
To see the impact of the newly introduced Error Prone checks [re-trigger the
integration test framework](#validating-changes-with-the-integration-tests).
## Part 3 (optional): Validating changes with the integration tests
We created an integration testing framework that allows us to see the impact of
the rules that are created. This testing framework can be executed locally and
via GitHub Actions.
If you still have more than 10-15 minutes left, and you want to test this
locally, run the following commands:
```sh
mvn clean install -DskipTests -Dverification.skip
./integration-tests/checkstyle-10.12.4.sh
```
Once the process is complete, and changes are introduced, the following output
will be printed:
```
There are unexpected changes.
Inspect the changes here: /tmp/tmp.Cmr423L1pA/checkstyle-10.12.4-diff-of-diffs-changes.patch
```
This file is be provided for your review using your preferred text editor.
Alternatively, you can also navigate to the repository by going to the
`./integration-tests/.repos/checkstyle` directory and executing `git log -p` to
view the commit history and associated changes.
This shows the impact of the rules that you wrote when they are applied to
Checkstyle!
The other option is to execute the integration test via GitHub Actions. You
only need to commit and push to the branch. This will trigger execution of the
integration tests, which will run for about 10 minutes. When the build is
finished, go to the _Actions_ tab in your fork and navigate to your most recent
commit and click on it. Then click on _Summary_ and download the artifact
`integration-test-checkstyle-10.12.4` at the bottom. Once done, unzip the
artifact and inspect the `checkstyle-10.12.4-diff-of-diffs-changes.patch` file
to see the changes.
[eps-github]: https://github.com/PicnicSupermarket/error-prone-support
[eps-workshop-jfall]: https://drive.google.com/file/d/1Es1OuSUmPHSt3BjeCWfrXfoF-cJkkA1A/view

150
workshop/pom.xml Normal file
View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.16.2-SNAPSHOT</version>
</parent>
<artifactId>workshop</artifactId>
<name>Picnic :: Error Prone Support :: Workshop</name>
<description>Project for the Error Prone Workshop.</description>
<url>https://error-prone.picnic.tech</url>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>error-prone-utils</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-java-11</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-templating</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleCompiler</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,33 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodTree;
/** A {@link BugChecker} that flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "Empty method can likely be deleted",
severity = WARNING,
tags = SIMPLIFICATION)
public final class Assignment0DeleteEmptyMethod extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link Assignment0DeleteEmptyMethod} instance. */
public Assignment0DeleteEmptyMethod() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Part 1: Ensure that we only delete methods that contain no statements.
// XXX: Part 2: Don't delete methods that are annotated with `@Override`.
return Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,61 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.nullLiteral;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodInvocationTree;
/**
* A {@link BugChecker} that flags AssertJ {@code isEqualTo(null)} checks for simplification.
*
* <p>This bug checker cannot be replaced with a simple Refaster rule, as the Refaster approach
* would require that all overloads of {@link org.assertj.core.api.Assert#isEqualTo(Object)} (such
* as {@link org.assertj.core.api.AbstractStringAssert#isEqualTo(String)}) are explicitly
* enumerated. This bug checker generically matches all such current and future overloads.
*/
@BugPattern(
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment1AssertJIsNullMethod extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.Assert").named("isEqualTo"),
argumentCount(1),
argument(0, nullLiteral()));
/** Instantiates a new {@link Assignment1AssertJIsNullMethod} instance. */
public Assignment1AssertJIsNullMethod() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
// This statement filters out `MethodInvocation`s that are *not* `assertThat().isEqualTo(null)`
// statements.
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fix = SuggestedFix.builder();
// XXX: Using `fix.merge(<some code>);` make sure we rename the method invocation to `isNull`.
// See the `SuggestedFixes` class ;).
tree.getArguments().forEach(arg -> fix.merge(SuggestedFix.delete(arg)));
return describeMatch(tree, fix.build());
}
}

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.utils.SourceCode;
/**
* A {@link BugChecker} that flags method invocations for which all arguments are wrapped using
* {@link org.mockito.Mockito#eq}; this is redundant.
*/
@BugPattern(
summary = "Don't unnecessarily use Mockito's `eq(...)`",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment2DropMockitoEq extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
/** Instantiates a new {@link Assignment2DropMockitoEq} instance. */
public Assignment2DropMockitoEq() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
// XXX: Make sure to return `Description.NO_MATCH` if the `tree` doesn't have arguments, or if
// the `isEqInvocation` method below returns `false` for at least one of the arguments.
SuggestedFix.Builder suggestedFix = SuggestedFix.builder();
for (ExpressionTree arg : tree.getArguments()) {
suggestedFix.replace(
arg,
SourceCode.treeToString(
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
}
return describeMatch(tree, suggestedFix.build());
}
@SuppressWarnings("UnusedMethod" /* Recommended to use when implementing this assignment. */)
private static boolean isEqInvocation(ExpressionTree tree, VisitorState state) {
return tree instanceof MethodInvocationTree && MOCKITO_EQ_METHOD.matches(tree, state);
}
}

View File

@@ -0,0 +1,54 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.isType;
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.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.List;
import tech.picnic.errorprone.utils.SourceCode;
/** A {@link BugChecker} that flags redundant {@code @Autowired} constructor annotations. */
@BugPattern(
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Assignment3DropAutowiredConstructorAnnotation extends BugChecker
implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
/** Instantiates a new {@link Assignment3DropAutowiredConstructorAnnotation} instance. */
public Assignment3DropAutowiredConstructorAnnotation() {}
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
// XXX: Using the `ASTHelpers#getConstructors` method, return `Description.NO_MATCH` if we do
// not have exactly 1 constructor (start by dropping `new ArrayList<>()` on the next line).
List<MethodTree> constructors = new ArrayList<>();
MultiMatchResult<AnnotationTree> hasAutowiredAnnotation =
AUTOWIRED_ANNOTATION.multiMatchResult(Iterables.getOnlyElement(constructors), state);
if (!hasAutowiredAnnotation.matches()) {
return Description.NO_MATCH;
}
AnnotationTree annotation = hasAutowiredAnnotation.onlyMatchingNode();
return describeMatch(annotation, SourceCode.deleteWithTrailingWhitespace(annotation, state));
}
}

View File

@@ -0,0 +1,61 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
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;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import javax.lang.model.element.Modifier;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "JUnit method declaration can likely be improved",
severity = WARNING,
tags = SIMPLIFICATION)
@SuppressWarnings({
"UnusedMethod",
"UnusedVariable"
} /* This check is yet to be implemented as part of the demo. */)
public final class Assignment4JUnitTestMethodDeclaration extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(AT_LEAST_ONE, anyOf(isType("org.junit.jupiter.api.Test")));
/** Instantiates a new {@link Assignment4JUnitTestMethodDeclaration} instance. */
public Assignment4JUnitTestMethodDeclaration() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Part 1: Return `Description.NO_MATCH` if the method is not a `TEST_METHOD`.
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
// XXX: Part 2: Make sure that JUnit test methods don't use any of the modifiers from the
// `ILLEGAL_MODIFIERS` field, by using `SuggestedFixes#removeModifiers` and
// `SuggestedFix.Builder#merge`.
if (fixBuilder.isEmpty()) {
return Description.NO_MATCH;
}
return describeMatch(tree, fixBuilder.build());
}
}

View File

@@ -0,0 +1,108 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.TypesWithUndefinedEquality;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
/** A {@link BugChecker} that flags redundant identity conversions. */
@BugPattern(
summary = "Avoid or clarify identity conversions",
severity = WARNING,
tags = SIMPLIFICATION)
public final class Assignment5DeleteIdentityConversion extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableListMultimap",
"com.google.common.collect.ImmutableMap",
"com.google.common.collect.ImmutableMultimap",
"com.google.common.collect.ImmutableMultiset",
"com.google.common.collect.ImmutableRangeMap",
"com.google.common.collect.ImmutableRangeSet",
"com.google.common.collect.ImmutableSet",
"com.google.common.collect.ImmutableSetMultimap",
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClass("com.google.errorprone.matchers.Matchers")
.namedAnyOf("allOf", "anyOf"));
/** Instantiates a new {@link Assignment5DeleteIdentityConversion} instance. */
public Assignment5DeleteIdentityConversion() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
// XXX: Make sure we skip invocations that do not pass exactly one argument, by using the
// `tree`.
if (!IS_CONVERSION_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree sourceTree = arguments.get(0);
Type sourceType = ASTHelpers.getType(sourceTree);
Type resultType = ASTHelpers.getType(tree);
TargetType targetType = ASTHelpers.targetType(state);
if (sourceType == null || resultType == null || targetType == null) {
return Description.NO_MATCH;
}
if (!state.getTypes().isSameType(sourceType, resultType)
&& !isConvertibleWithWellDefinedEquality(sourceType, targetType.type(), state)) {
return Description.NO_MATCH;
}
if (sourceType.isPrimitive()
&& state.getPath().getParentPath().getLeaf() instanceof MemberSelectTree) {
return Description.NO_MATCH;
}
return buildDescription(tree)
// XXX: Use the `.addFix()` to suggest replacing the original `tree` with the `sourceTree`.
// Tip: You can get the actual String representation of a Tree by using the
// `SourceCode#treeToString`.
.build();
}
private static boolean isConvertibleWithWellDefinedEquality(
Type sourceType, Type targetType, VisitorState state) {
Types types = state.getTypes();
return !types.isSameType(targetType, OBJECT_TYPE.get(state))
&& types.isConvertible(sourceType, targetType)
&& Arrays.stream(TypesWithUndefinedEquality.values())
.noneMatch(b -> b.matchesType(sourceType, state) || b.matchesType(targetType, state));
}
}

View File

@@ -0,0 +1,22 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
/** Refaster rule used as example for the assignments of the workshop. */
final class WorkshopAssignment0Rules {
private WorkshopAssignment0Rules() {}
/** Prefer {@link String#isEmpty()} over alternatives that consult the string's length. */
static final class ExampleStringIsEmpty {
@BeforeTemplate
static boolean before(String string) {
return string.length() == 0;
}
@AfterTemplate
static boolean after(String string) {
return string.isEmpty();
}
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the first assignment of the workshop. */
final class WorkshopAssignment1Rules {
private WorkshopAssignment1Rules() {}
/** Prefer {@link String#String(char[])} over {@link String#copyValueOf(char[])}. */
static final class NewStringCharArray {
// XXX: Implement this Refaster rule.
}
}

View File

@@ -0,0 +1,34 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Collections;
import java.util.List;
/** Refaster rules for the second assignment of the workshop. */
final class WorkshopAssignment2Rules {
private WorkshopAssignment2Rules() {}
/**
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
static final class ImmutableListOfOne<T> {
@BeforeTemplate
static <T> List<T> before(T t) {
return ImmutableList.copyOf(
Refaster.anyOf(
Collections.singletonList(t),
List.of(t))
);
}
@AfterTemplate
static <T> List<T> after(T t) {
return ImmutableList.of(t);
}
}
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.base.Preconditions;
/** Refaster rules for the third assignment of the workshop. */
@SuppressWarnings("UnusedTypeParameter" /* Ignore this for demo purposes. */)
final class WorkshopAssignment3Rules {
private WorkshopAssignment3Rules() {}
// XXX: Tip: check the input and output files to see the *expected* refactoring.
/** Prefer {@link Preconditions#checkArgument(boolean)} over if statements. */
static final class CheckArgumentWithoutMessage {
// XXX: Implement the Refaster rule to get the test green.
}
/** Prefer {@link Preconditions#checkArgument(boolean, Object)} over if statements. */
static final class CheckArgumentWithMessage {
// XXX: Implement the Refaster rule to get the test green.
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the fourth assignment of the workshop. */
@SuppressWarnings("java:S1698" /* Reference comparison is valid for enums. */)
final class WorkshopAssignment4Rules {
private WorkshopAssignment4Rules() {}
// The test fails because non Enum comparisons are also rewritten.
// Fix the test by tweaking the type parameters.
// XXX: Get the test to pass by improving the Refaster rule (uncommented it first).
// static final class PrimitiveOrReferenceEqualityEnum<T> {
// @BeforeTemplate
// boolean before(T a, T b) {
// return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
// }
//
// @AfterTemplate
// @AlsoNegation
// boolean after(T a, T b) {
// return a == b;
// }
// }
}

View File

@@ -0,0 +1,12 @@
package tech.picnic.errorprone.workshop.refasterrules;
/** Refaster rules for the fifth assignment of the workshop. */
@SuppressWarnings("UnusedTypeParameter" /* Ignore this for demo purposes. */)
final class WorkshopAssignment5Rules {
private WorkshopAssignment5Rules() {}
abstract static class StreamDoAllMatch<T> {
// XXX: Implement the Refaster rule to get the test green.
// Tip: use the `@Placeholder` annotation.
}
}

View File

@@ -0,0 +1,70 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment0Assignment0DeleteEmptyMethodTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment0DeleteEmptyMethod.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" Object m1() {",
" return null;",
" }",
"",
" void m2() {",
" System.out.println(42);",
" }",
"",
" // BUG: Diagnostic contains:",
" static void m3() {}",
"",
" interface F {",
" void fun();",
" }",
"}")
.addSourceLines(
"B.java",
"final class B implements A.F {",
" @Override",
" public void fun() {}",
"",
" /** Javadoc. */",
" // BUG: Diagnostic contains:",
" void m4() {}",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment0DeleteEmptyMethod.class, getClass())
.addInputLines(
"A.java",
"final class A {",
"",
" void instanceMethod() {}",
"",
" static void staticMethod() {}",
"",
" static void staticMethodWithComment() {",
" System.out.println(42);",
" }",
"}")
.addOutputLines(
"A.java",
"final class A {",
"",
" static void staticMethodWithComment() {",
" System.out.println(42);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,60 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment1AssertJIsNullMethodTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment1AssertJIsNullMethod.class, getClass())
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(1);",
" // BUG: Diagnostic contains:",
" assertThat(1).isEqualTo(null);",
" // BUG: Diagnostic contains:",
" assertThat(\"foo\").isEqualTo(null);",
" isEqualTo(null);",
" }",
"",
" private boolean isEqualTo(Object value) {",
" return value.equals(\"bar\");",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment1AssertJIsNullMethod.class, getClass())
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(null);",
" assertThat(\"foo\").isEqualTo(null);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isNull();",
" assertThat(\"foo\").isNull();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,102 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment2DropMockitoEqTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment2DropMockitoEq.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.ArgumentMatchers.notNull;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Runnable runnable = mock(Runnable.class);",
" doAnswer(inv -> null).when(runnable).run();",
"",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(notNull());",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(eq(0), notNull());",
" doAnswer(inv -> null).when(biConsumer).accept(notNull(), eq(\"foo\"));",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" // BUG: Diagnostic contains:",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(Assignment2DropMockitoEq.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null)",
" .when(biConsumer)",
" .accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(toString());",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(hashCode(), toString());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,106 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment3DropAutowiredConstructorAnnotationTest {
@Test
void identification() {
CompilationTestHelper.newInstance(
Assignment3DropAutowiredConstructorAnnotation.class, getClass())
.addSourceLines(
"Container.java",
"import com.google.errorprone.annotations.Immutable;",
"import java.util.List;",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" @Immutable",
" class A {",
" A() {}",
" }",
"",
" class B {",
" @Autowired",
" void setProperty(Object o) {}",
" }",
"",
" class C {",
" // BUG: Diagnostic contains:",
" @Autowired",
" C() {}",
" }",
"",
" class D {",
" // BUG: Diagnostic contains:",
" @Autowired",
" D(String x) {}",
" }",
"",
" class E {",
" @Autowired",
" E() {}",
"",
" E(String x) {}",
" }",
"",
" class F {",
" F() {}",
"",
" @Autowired",
" F(String x) {}",
" }",
"",
" class G {",
" @Autowired private Object o;",
" }",
"",
" class H {",
" @SafeVarargs",
" H(List<String>... lists) {}",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment3DropAutowiredConstructorAnnotation.class, getClass())
.addInputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" class A {",
" @Autowired",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
" @Autowired",
" B(String x) {}",
" }",
"}")
.addOutputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",
"",
"interface Container {",
" class A {",
" @Deprecated",
" A() {}",
" }",
"",
" class B {",
" B(String x) {}",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,91 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this to validate part 1.")
final class Assignment4JUnitTestMethodDeclarationTest {
@Test
void identificationIllegalModifiers() {
CompilationTestHelper.newInstance(Assignment4JUnitTestMethodDeclaration.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void method1() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" public void method2() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" protected void method3() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" private void method4() {}",
"",
" public void method5() {}",
"",
" protected void method6() {}",
"",
" private void method7() {}",
"}")
.doTest();
}
@Test
void replacementIllegalModifiers() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment4JUnitTestMethodDeclaration.class, getClass())
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void foo() {}",
"",
" @Test",
" public void bar() {}",
"",
" @Test",
" protected void baz() {}",
"",
" @Test",
" private void qux() {}",
"",
" public void quux() {}",
"",
" private void quuz() {}",
"}")
.addOutputLines(
"A.java",
"import org.junit.jupiter.api.Test;",
"",
"class A {",
" @Test",
" void foo() {}",
"",
" @Test",
" void bar() {}",
"",
" @Test",
" void baz() {}",
"",
" @Test",
" void qux() {}",
"",
" public void quux() {}",
"",
" private void quuz() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,223 @@
package tech.picnic.errorprone.workshop.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@Disabled("Enable this when implementing the BugChecker.")
final class Assignment5DeleteIdentityConversionTest {
@Test
void identification() {
CompilationTestHelper.newInstance(Assignment5DeleteIdentityConversion.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"",
"import com.google.common.collect.ImmutableBiMap;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableListMultimap;",
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableMultimap;",
"import com.google.common.collect.ImmutableMultiset;",
"import com.google.common.collect.ImmutableRangeMap;",
"import com.google.common.collect.ImmutableRangeSet;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSetMultimap;",
"import com.google.common.collect.ImmutableTable;",
"import com.google.errorprone.matchers.Matcher;",
"import com.google.errorprone.matchers.Matchers;",
"",
"public final class A {",
" public void m() {",
" // BUG: Diagnostic contains:",
" Matcher allOf1 = Matchers.allOf(instanceMethod());",
" Matcher allOf2 = Matchers.allOf(instanceMethod(), staticMethod());",
" // BUG: Diagnostic contains:",
" Matcher anyOf1 = Matchers.anyOf(staticMethod());",
" Matcher anyOf2 = Matchers.anyOf(instanceMethod(), staticMethod());",
"",
" // BUG: Diagnostic contains:",
" Boolean b1 = Boolean.valueOf(Boolean.FALSE);",
" // BUG: Diagnostic contains:",
" Boolean b2 = Boolean.valueOf(false);",
" // BUG: Diagnostic contains:",
" boolean b3 = Boolean.valueOf(Boolean.FALSE);",
" // BUG: Diagnostic contains:",
" boolean b4 = Boolean.valueOf(false);",
"",
" // BUG: Diagnostic contains:",
" Byte byte1 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" Byte byte2 = Byte.valueOf(Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" byte byte3 = Byte.valueOf((Byte) Byte.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" byte byte4 = Byte.valueOf(Byte.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" Character c1 = Character.valueOf((Character) 'a');",
" // BUG: Diagnostic contains:",
" Character c2 = Character.valueOf('a');",
" // BUG: Diagnostic contains:",
" char c3 = Character.valueOf((Character) 'a');",
" // BUG: Diagnostic contains:",
" char c4 = Character.valueOf('a');",
"",
" // BUG: Diagnostic contains:",
" Double d1 = Double.valueOf((Double) 0.0);",
" // BUG: Diagnostic contains:",
" Double d2 = Double.valueOf(0.0);",
" // BUG: Diagnostic contains:",
" double d3 = Double.valueOf((Double) 0.0);",
" // BUG: Diagnostic contains:",
" double d4 = Double.valueOf(0.0);",
"",
" // BUG: Diagnostic contains:",
" Float f1 = Float.valueOf((Float) 0.0F);",
" // BUG: Diagnostic contains:",
" Float f2 = Float.valueOf(0.0F);",
" // BUG: Diagnostic contains:",
" float f3 = Float.valueOf((Float) 0.0F);",
" // BUG: Diagnostic contains:",
" float f4 = Float.valueOf(0.0F);",
"",
" // BUG: Diagnostic contains:",
" Integer i1 = Integer.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" Integer i2 = Integer.valueOf(1);",
" // BUG: Diagnostic contains:",
" int i3 = Integer.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" int i4 = Integer.valueOf(1);",
"",
" // BUG: Diagnostic contains:",
" Long l1 = Long.valueOf((Long) 1L);",
" // BUG: Diagnostic contains:",
" Long l2 = Long.valueOf(1L);",
" // BUG: Diagnostic contains:",
" long l3 = Long.valueOf((Long) 1L);",
" // BUG: Diagnostic contains:",
" long l4 = Long.valueOf(1L);",
"",
" Long l5 = Long.valueOf((Integer) 1);",
" Long l6 = Long.valueOf(1);",
" // BUG: Diagnostic contains:",
" long l7 = Long.valueOf((Integer) 1);",
" // BUG: Diagnostic contains:",
" long l8 = Long.valueOf(1);",
"",
" // BUG: Diagnostic contains:",
" Short s1 = Short.valueOf((Short) Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" Short s2 = Short.valueOf(Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" short s3 = Short.valueOf((Short) Short.MIN_VALUE);",
" // BUG: Diagnostic contains:",
" short s4 = Short.valueOf(Short.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" String boolStr = Boolean.valueOf(Boolean.FALSE).toString();",
" int boolHash = Boolean.valueOf(false).hashCode();",
" // BUG: Diagnostic contains:",
" int byteHash = Byte.valueOf((Byte) Byte.MIN_VALUE).hashCode();",
" String byteStr = Byte.valueOf(Byte.MIN_VALUE).toString();",
"",
" String str1 = String.valueOf(0);",
" // BUG: Diagnostic contains:",
" String str2 = String.valueOf(\"1\");",
"",
" // BUG: Diagnostic contains:",
" ImmutableBiMap<Object, Object> o1 = ImmutableBiMap.copyOf(ImmutableBiMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableList<Object> o2 = ImmutableList.copyOf(ImmutableList.of());",
" ImmutableListMultimap<Object, Object> o3 =",
" // BUG: Diagnostic contains:",
" ImmutableListMultimap.copyOf(ImmutableListMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMap<Object, Object> o4 = ImmutableMap.copyOf(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultimap<Object, Object> o5 = ImmutableMultimap.copyOf(ImmutableMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableMultiset<Object> o6 = ImmutableMultiset.copyOf(ImmutableMultiset.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeMap<String, Object> o7 = ImmutableRangeMap.copyOf(ImmutableRangeMap.of());",
" // BUG: Diagnostic contains:",
" ImmutableRangeSet<String> o8 = ImmutableRangeSet.copyOf(ImmutableRangeSet.of());",
" // BUG: Diagnostic contains:",
" ImmutableSet<Object> o9 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSetMultimap<Object, Object> o10 =",
" // BUG: Diagnostic contains:",
" ImmutableSetMultimap.copyOf(ImmutableSetMultimap.of());",
" // BUG: Diagnostic contains:",
" ImmutableTable<Object, Object, Object> o11 = ImmutableTable.copyOf(ImmutableTable.of());",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(
Assignment5DeleteIdentityConversion.class, getClass())
.addInputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.copyOf(ImmutableList.of(1));",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.copyOf(ImmutableSet.of(1));",
" Collection<Integer> c2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.copyOf(ImmutableSet.of());",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.of();",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
" ImmutableCollection<Integer> list1 = ImmutableList.of(1);",
" ImmutableCollection<Integer> list2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Collection<Integer> c1 = ImmutableSet.of(1);",
" Collection<Integer> c2 = ImmutableList.copyOf(new ArrayList<>(ImmutableList.of(1)));",
"",
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.of();",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,37 @@
package tech.picnic.errorprone.workshop.refasterrules;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollection;
final class WorkshopRefasterRulesTest {
@Test
void validateExampleRuleCollection() {
RefasterRuleCollection.validate(WorkshopAssignment0Rules.class);
}
@Test
void validateFirstWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment1Rules.class);
}
@Test
void validateSecondWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment2Rules.class);
}
@Test
void validateThirdWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment3Rules.class);
}
@Test
void validateFourthWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment4Rules.class);
}
@Test
void validateFifthWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment5Rules.class);
}
}

View File

@@ -0,0 +1,10 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment0RulesTest implements RefasterRuleCollectionTestCase {
boolean testExampleStringIsEmpty() {
boolean b = "foo".length() == 0;
return "bar".length() == 0;
}
}

View File

@@ -0,0 +1,10 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment0RulesTest implements RefasterRuleCollectionTestCase {
boolean testExampleStringIsEmpty() {
boolean b = "foo".isEmpty();
return "bar".isEmpty();
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment1RulesTest implements RefasterRuleCollectionTestCase {
String testNewStringCharArray() {
String.copyValueOf(new char[] {});
String.copyValueOf(new char[] {'f', 'o', 'o'});
return String.copyValueOf(new char[] {'b', 'a', 'r'});
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment1RulesTest implements RefasterRuleCollectionTestCase {
String testNewStringCharArray() {
new String(new char[] {});
new String(new char[] {'f', 'o', 'o'});
return new String(new char[] {'b', 'a', 'r'});
}
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment2RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
List<Object> testImmutableListOfOne() {
Collections.singletonList(1);
Collections.singletonList("foo");
List.of(1);
return List.of("bar");
}
}

View File

@@ -0,0 +1,21 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.List;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment2RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
List<Object> testImmutableListOfOne() {
ImmutableList.of(1);
ImmutableList.of("foo");
ImmutableList.of(1);
return ImmutableList.of("bar");
}
}

View File

@@ -0,0 +1,24 @@
package tech.picnic.errorprone.workshop.refasterrules;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException();
}
if (!"bar".isEmpty()) {
throw new IllegalArgumentException();
}
}
void testCheckArgumentWithMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException("The string is not empty");
}
if (!"bar".isEmpty()) {
throw new IllegalArgumentException(
"The rule should be able rewrite all kinds of messages ;).");
}
}
}

View File

@@ -0,0 +1,17 @@
package tech.picnic.errorprone.workshop.refasterrules;
import static com.google.common.base.Preconditions.checkArgument;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
checkArgument("foo".isEmpty());
checkArgument("bar".isEmpty());
}
void testCheckArgumentWithMessage() {
checkArgument("foo".isEmpty(), "The string is not empty");
checkArgument("bar".isEmpty(), "The rule should be able rewrite all kinds of messages ;).");
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Objects;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment4RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class);
}
ImmutableSet<Boolean> testPrimitiveOrReferenceEqualityEnum() {
// Don't pay attention to the `ImmutableSet#of` here, it's just syntactical sugar for the
// testing framework.
return ImmutableSet.of(
"foo".equals("bar"),
Integer.valueOf(1).equals(2),
RoundingMode.UP.equals(RoundingMode.DOWN),
Objects.equals(RoundingMode.UP, RoundingMode.DOWN),
!RoundingMode.UP.equals(RoundingMode.DOWN),
!Objects.equals(RoundingMode.UP, RoundingMode.DOWN));
}
}

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
import java.util.Objects;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment4RulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class);
}
ImmutableSet<Boolean> testPrimitiveOrReferenceEqualityEnum() {
// Don't pay attention to the `ImmutableSet#of` here, it's just syntactical sugar for the
// testing framework.
return ImmutableSet.of(
"foo".equals("bar"),
Integer.valueOf(1).equals(2),
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP == RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN,
RoundingMode.UP != RoundingMode.DOWN);
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment5RulesTest implements RefasterRuleCollectionTestCase {
boolean testStreamDoAllMatch() {
boolean example = Stream.of("foo").noneMatch(s -> !s.isBlank());
return Stream.of("bar").noneMatch(b -> !b.startsWith("b"));
}
}

View File

@@ -0,0 +1,11 @@
package tech.picnic.errorprone.workshop.refasterrules;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment5RulesTest implements RefasterRuleCollectionTestCase {
boolean testStreamDoAllMatch() {
boolean example = Stream.of("foo").allMatch(s -> s.isBlank());
return Stream.of("bar").allMatch(b -> b.startsWith("b"));
}
}