Compare commits

..

34 Commits

Author SHA1 Message Date
Rick Ossendrijver
90fb015fa0 Update EP and version on workshop branch 2025-04-16 11:22:38 +02:00
Rick Ossendrijver
1d457c73e1 Dont run all the checks by default 2025-04-16 11:20:00 +02:00
Rick Ossendrijver
06aa53e9b9 Add fix for illegalaccess 2025-04-16 11:16:54 +02:00
Rick Ossendrijver
f45899fba8 Updates slide link 2025-04-16 11:16:40 +02:00
Rick Ossendrijver
7b8a53ca9b Add -Dstyle.color=always to the maven.config 2025-04-16 11:16:40 +02:00
Rick Ossendrijver
e5045b0fdc Update version in workshop module 2025-04-16 11:16:39 +02:00
Rick Ossendrijver
d028146bd6 Make dependency provided 2025-04-16 11:16:39 +02:00
Rick Ossendrijver
2843f47d67 Fix for @Generated problem with OpenRewrite 2025-04-16 11:16:38 +02:00
Rick Ossendrijver
c87db5536d Add link to presentation 2025-04-16 11:16:38 +02:00
Rick Ossendrijver
561c8caf77 Correct version in pom 2025-04-16 11:16:37 +02:00
Rick Ossendrijver
a279585a02 Introduce trouble shooting page 2025-04-16 11:16:37 +02:00
Rick Ossendrijver
6312386ce2 Updates slides and move README section 2025-04-16 11:16:37 +02:00
Rick Ossendrijver
f1c76411a3 Add extra explanation 2025-04-16 11:16:36 +02:00
Rick Ossendrijver
f773f6e686 Improve the third example 2025-04-16 11:16:36 +02:00
Rick Ossendrijver
fe3e065394 Introduce workshop module 2025-04-16 11:16:34 +02:00
Picnic-DevPla-Bot
bedf588e2a Upgrade Micrometer 1.14.5 -> 1.14.6 (#1641)
See:
- https://github.com/micrometer-metrics/micrometer/releases/tag/v1.14.6
- https://github.com/micrometer-metrics/micrometer/compare/v1.14.5...v1.14.6
2025-04-16 09:24:00 +02:00
Stephan Schroevers
3a629b11ec Update Error Prone compatibility matrix (#1642) 2025-04-16 08:23:54 +02:00
Stephan Schroevers
fc2eb57b40 [maven-release-plugin] prepare for next development iteration 2025-04-14 11:36:15 +02:00
Stephan Schroevers
1c429f0d1c [maven-release-plugin] prepare release v0.22.0 2025-04-14 11:36:15 +02:00
Mohamed Sameh
ac6ffc72e9 Avoid invalid ExplicitArgumentEnumeration suggestions (#1637)
While still imperfect, this change improves the heuristics that 
determine whether an explicit `Iterable` creator invocation can be 
unwrapped, such that the enumerated values will be passed to a 
compatible varargs overload instead. The new logic validates that the
overload accepts values of the appropriate type.
2025-04-14 11:33:20 +02:00
Picnic-DevPla-Bot
07399bb4ef Upgrade Guava 33.4.0-jre -> 33.4.7-jre (#1598)
See:
- https://guava.dev/releases/33.4.7-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v33.4.1
- https://github.com/google/guava/releases/tag/v33.4.2
- https://github.com/google/guava/releases/tag/v33.4.3
- https://github.com/google/guava/releases/tag/v33.4.4
- https://github.com/google/guava/releases/tag/v33.4.5
- https://github.com/google/guava/releases/tag/v33.4.6
- https://github.com/google/guava/releases/tag/v33.4.7
- https://github.com/google/guava/compare/v33.4.0...v33.4.7
2025-04-14 10:22:02 +02:00
Stephan Schroevers
aa988ef2b0 Run NullAway in JSpecify mode (#1608) 2025-04-14 09:42:16 +02:00
Picnic-DevPla-Bot
b58be2b89b Upgrade NullAway 0.12.5 -> 0.12.6 (#1616)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/releases/tag/v0.12.6
- https://github.com/uber/NullAway/compare/v0.12.5...v0.12.6
2025-04-14 09:42:16 +02:00
Picnic-DevPla-Bot
8fa90f6121 Upgrade Swagger 2.2.29 -> 2.2.30 (#1633)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.30
- https://github.com/swagger-api/swagger-core/compare/v2.2.29...v2.2.30
2025-04-14 08:02:35 +02:00
Picnic-DevPla-Bot
6ba03c6352 Upgrade AspectJ 1.9.23 -> 1.9.24 (#1638)
See:
- https://github.com/eclipse-aspectj/aspectj/releases/tag/V1_9_24
- https://github.com/eclipse/org.aspectj/compare/V1_9_23...V1_9_24
2025-04-13 23:48:40 +02:00
Picnic-DevPla-Bot
912e06885d Upgrade JUnit 5 5.12.1 -> 5.12.2 (#1639)
See:
- https://junit.org/junit5/docs/current/release-notes/
- https://github.com/junit-team/junit5/releases/tag/r5.12.2
- https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2
2025-04-13 21:09:28 +02:00
Picnic-DevPla-Bot
e803a5d333 Upgrade OpenRewrite Templating 1.24.2 -> 1.25.1 (#1636)
See:
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.25.0
- https://github.com/openrewrite/rewrite-templating/releases/tag/v1.25.1
- https://github.com/openrewrite/rewrite-templating/compare/v1.24.2...v1.25.1
2025-04-12 10:41:29 +02:00
Picnic-DevPla-Bot
4a1ad1b991 Upgrade OpenRewrite 3.5.0 -> 3.6.1 (#1635)
See:
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v3.6.0
- https://github.com/openrewrite/rewrite-recipe-bom/releases/tag/v3.6.1
- https://github.com/openrewrite/rewrite-recipe-bom/compare/v3.5.0...v3.6.1
2025-04-12 10:19:46 +02:00
Picnic-DevPla-Bot
80d433f051 Upgrade pitest-maven-plugin 1.19.0 -> 1.19.1 (#1634)
See:
- https://github.com/hcoles/pitest/releases/tag/1.19.1
- https://github.com/hcoles/pitest/compare/1.19.0...1.19.1
2025-04-10 10:37:37 +02:00
Stephan Schroevers
afd9703ce6 Extend EnumerableAssertHasSameSizeAs Refaster rule (#1632) 2025-04-07 12:48:46 +02:00
Picnic-DevPla-Bot
f9d310bc4b Upgrade Pitest Git plugins 2.2.0 -> 2.2.1 (#1618) 2025-04-07 10:16:56 +02:00
Picnic-DevPla-Bot
a3a9ca0bf0 Upgrade step-security/harden-runner v2.11.0 -> v2.11.1 (#1625)
See:
- https://github.com/step-security/harden-runner/releases/tag/v2.11.1
2025-04-07 09:57:11 +02:00
Picnic-DevPla-Bot
0e609aeb1c Upgrade Checker Framework Annotations 3.49.1 -> 3.49.2 (#1627)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.49.2
- https://github.com/typetools/checker-framework/compare/checker-framework-3.49.1...checker-framework-3.49.2
2025-04-07 09:16:29 +02:00
Picnic-DevPla-Bot
e9dae1d66c Upgrade Checkstyle 10.22.0 -> 10.23.0 (#1629)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.23.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.22.0...checkstyle-10.23.0
2025-04-07 09:02:09 +02:00
86 changed files with 2378 additions and 1397 deletions

View File

@@ -26,7 +26,7 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block
@@ -75,7 +75,7 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -7,24 +7,22 @@
# https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object
name: "Integration tests"
on:
# issue_comment:
# types: [ created ]
pull_request:
issue_comment:
types: [ created ]
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')
if: |
github.event.issue.pull_request && contains(github.event.comment.body, '/integration-test')
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
integration-test: [ "metrics", "prometheus-java-client" ] # XXX: Dropped checkstyle.
integration-test: [ "checkstyle", "metrics", "prometheus-java-client" ]
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block
@@ -47,14 +45,13 @@ jobs:
- name: Check out code and set up JDK and Maven
uses: s4u/setup-maven-action@4f7fb9d9675e899ca81c6161dadbba0189a4ebb1 # v1.18.0
with:
# checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
checkout-ref: "refs/pull/${{ github.event.issue.number }}/head"
java-version: 17.0.13
java-distribution: temurin
maven-version: 3.9.9
- name: Install project to local Maven repository
run: mvn -T1C install -DskipTests -Dverification.skip
- name: Run integration test
continue-on-error: true
run: xvfb-run "./integration-tests/${{ matrix.integration-test }}.sh" "${{ runner.temp }}/artifacts"
- name: Upload artifacts on failure
if: ${{ failure() }}

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Install Harden-Runner
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
disable-sudo: true
egress-policy: block

View File

@@ -1,3 +1,4 @@
--batch-mode
--errors
--strict-checksums
-Dstyle.color=always

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar

View File

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

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.documentation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.Objects.requireNonNull;
import static java.util.function.Predicate.not;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -167,7 +168,10 @@ public final class BugPatternTestExtractor implements Extractor<BugPatternTestCa
* is safe, because this code is guarded by an earlier call to `#getClassUnderTest(..)`,
* which ensures that `tree` is part of a longer method invocation chain.
*/
MethodInvocationTree inputTree = (MethodInvocationTree) ASTHelpers.getReceiver(tree);
MethodInvocationTree inputTree =
(MethodInvocationTree)
requireNonNull(
ASTHelpers.getReceiver(tree), "Instance method invocation must have receiver");
String path = ASTHelpers.constValue(inputTree.getArguments().get(0), String.class);
Optional<String> inputCode = getSourceCode(inputTree);

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.21.1-SNAPSHOT</version>
<version>0.22.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -62,6 +62,16 @@
<artifactId>auto-value-annotations</artifactId>
<scope>provided</scope>
</dependency>
<!-- XXX: JSR-305 (meta-)annotation usage by some dependencies triggers
NullAway to attempt to load said annotations. As such some modules
require these annotations to be on the classpath. Periodically review
whether we can drop this dependeny declaration. See
https://github.com/uber/NullAway/issues/1171. -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotation</artifactId>
@@ -303,9 +313,6 @@
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
</compilerArgs>
</configuration>
<executions>
<!-- The Refaster input/output test classes used by

View File

@@ -11,6 +11,7 @@ import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.returnStatement;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
@@ -79,7 +80,10 @@ public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
return Description.NO_MATCH;
}
Symbol variableSymbol = ASTHelpers.getSymbol(((ReturnTree) finalStatement).getExpression());
Symbol variableSymbol =
requireNonNull(
ASTHelpers.getSymbol(((ReturnTree) finalStatement).getExpression()),
"Missing symbol for returned variable");
StatementTree precedingStatement = statements.get(statements.size() - 2);
return tryMatchAssignment(variableSymbol, precedingStatement)

View File

@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -33,6 +34,8 @@ import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -178,30 +181,45 @@ public final class ExplicitArgumentEnumeration extends BugChecker
state.getTypes())
.collect(toImmutableList());
/*
* If all overloads have a single parameter, and at least one of them is a varargs method, then
* we assume that unwrapping the iterable argument will cause a suitable overload to be invoked.
* (Note that there may be multiple varargs overloads, either with different parameter types, or
* due to method overriding; this check does not attempt to determine which exact method or
* overload will be invoked as a result of the suggested simplification.)
*
* Note that this is a (highly!) imperfect heuristic, but it is sufficient to prevent e.g.
* unwrapping of arguments to `org.jooq.impl.DSL#row`, which can cause the expression's return
* type to change from `RowN` to (e.g.) `Row2`.
*/
// XXX: There are certainly cases where it _would_ be nice to unwrap the arguments to
// `org.jooq.impl.DSL#row(Collection<?>)`. Look into this.
// XXX: Ideally we do check that one of the overloads accepts the unwrapped arguments.
// XXX: Ideally we validate that eligible overloads have compatible return types.
boolean hasLikelySuitableVarargsOverload =
overloads.stream().allMatch(m -> m.params().size() == 1)
&& overloads.stream().anyMatch(MethodSymbol::isVarArgs);
return hasLikelySuitableVarargsOverload
return hasLikelySuitableVarargsOverload(method, overloads, state)
? Optional.of(SourceCode.unwrapMethodInvocation(argument, state))
: Optional.empty();
}
/**
* Tells whether it is likely that, if the argument to the given method is unwrapped, a suitable
* varargs overload will be invoked instead.
*
* <p>If all overloads have a single parameter, and at least one of them is a suitably-typed
* varargs method, then we assume that unwrapping the iterable argument will cause a suitable
* overload to be invoked. (Note that there may be multiple varargs overloads due to method
* overriding; this check does not attempt to determine which exact method or overload will be
* invoked as a result of the suggested simplification.)
*
* <p>Note that this is a (highly!) imperfect heuristic, but it is sufficient to prevent e.g.
* unwrapping of arguments to `org.jooq.impl.DSL#row`, which can cause the expression's return
* type to change from `RowN` to (e.g.) `Row2`.
*/
// XXX: There are certainly cases where it _would_ be nice to unwrap the arguments to
// `org.jooq.impl.DSL#row(Collection<?>)`. Look into this.
// XXX: Ideally we validate that eligible overloads have compatible return types.
private static boolean hasLikelySuitableVarargsOverload(
MethodSymbol method, ImmutableList<MethodSymbol> overloads, VisitorState state) {
Types types = state.getTypes();
// XXX: This logic is fragile, as it assumes that the method parameter's type is of the form
// `X<T>`, where `T` is the type of the explicitly enumerated values passed to the expression to
// be unwrapped. This should generally hold, given the types returned by the
// `EXPLICIT_ITERABLE_CREATOR` expressions: `Iterable<T>`, `List<T>`, `Set<T>`, etc.
Type parameterType =
Iterables.getOnlyElement(
Iterables.getOnlyElement(method.getParameters()).type.getTypeArguments());
return overloads.stream().allMatch(m -> m.params().size() == 1)
&& overloads.stream()
.filter(MethodSymbol::isVarArgs)
.map(m -> types.elemtype(Iterables.getOnlyElement(m.getParameters()).type))
.anyMatch(varArgsType -> types.containsType(parameterType, varArgsType));
}
private static Optional<SuggestedFix> trySuggestCallingCustomAlternative(
MethodInvocationTree tree, MethodInvocationTree argument, VisitorState state) {
return ALTERNATIVE_METHODS.rowMap().entrySet().stream()

View File

@@ -17,6 +17,7 @@ import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static java.util.Objects.requireNonNull;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
@@ -129,7 +130,10 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
return Description.NO_MATCH;
}
Type parameterType = ASTHelpers.getType(Iterables.getOnlyElement(tree.getParameters()));
Type parameterType =
requireNonNull(
ASTHelpers.getType(Iterables.getOnlyElement(tree.getParameters())),
"Missing type for method parameter");
return findMethodSourceAnnotation(tree, state)
.flatMap(
@@ -173,7 +177,9 @@ public final class JUnitValueSource extends BugChecker implements MethodTreeMatc
private static Optional<MethodTree> findMatchingSibling(
MethodTree tree, Predicate<? super MethodTree> predicate, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
return requireNonNull(state.findEnclosing(ClassTree.class), "No class enclosing method")
.getMembers()
.stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(not(tree::equals))

View File

@@ -5,6 +5,7 @@ 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.instanceMethod;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
@@ -86,7 +87,9 @@ public final class NonEmptyMono extends BugChecker implements MethodInvocationTr
return Description.NO_MATCH;
}
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
ExpressionTree receiver =
requireNonNull(
ASTHelpers.getReceiver(tree), "Instance method invocation must have receiver");
if (!NON_EMPTY_MONO.matches(receiver, state)) {
return Description.NO_MATCH;
}

View File

@@ -204,6 +204,11 @@ final class AssertJEnumerableRules {
return enumAssert.hasSize(iterable.length);
}
@BeforeTemplate
EnumerableAssert<?, S> before(EnumerableAssert<?, S> enumAssert, CharSequence iterable) {
return enumAssert.hasSize(iterable.length());
}
@AfterTemplate
EnumerableAssert<?, S> after(EnumerableAssert<?, S> enumAssert, Iterable<E> iterable) {
return enumAssert.hasSameSizeAs(iterable);

View File

@@ -515,8 +515,14 @@ final class ReactorRules {
mono.switchIfEmpty(Mono.empty()), mono.flux().next(), mono.flux().singleOrEmpty());
}
// XXX: Consider filing a SonarCloud issue for the S2637 false positive.
@BeforeTemplate
Mono<@Nullable Void> before2(Mono<@Nullable Void> mono) {
@SuppressWarnings({
"java:S2637" /* False positive: result is never `null`. */,
"java:S4968" /* Result may be `Mono<Void>`. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
Mono<? extends @Nullable Void> before2(Mono<@Nullable Void> mono) {
return Refaster.anyOf(mono.ignoreElement(), mono.then());
}
@@ -945,7 +951,8 @@ final class ReactorRules {
/** Prefer direct invocation of {@link Mono#then()}} over more contrived alternatives. */
static final class MonoThen<T> {
@BeforeTemplate
Mono<@Nullable Void> before(Mono<T> mono) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before(Mono<T> mono) {
return Refaster.anyOf(
mono.ignoreElement().then(),
mono.flux().then(),
@@ -954,7 +961,8 @@ final class ReactorRules {
}
@AfterTemplate
Mono<@Nullable Void> after(Mono<T> mono) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> after(Mono<T> mono) {
return mono.then();
}
}
@@ -962,17 +970,25 @@ final class ReactorRules {
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
static final class FluxThen<T> {
@BeforeTemplate
Mono<@Nullable Void> before(Flux<T> flux) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before(Flux<T> flux) {
return flux.ignoreElements().then();
}
// XXX: Consider filing a SonarCloud issue for the S2637 false positive.
@BeforeTemplate
Mono<@Nullable Void> before2(Flux<@Nullable Void> flux) {
@SuppressWarnings({
"java:S2637" /* False positive: result is never `null`. */,
"java:S4968" /* Result may be `Mono<Void>`. */,
"key-to-resolve-AnnotationUseStyle-and-TrailingComment-check-conflict"
})
Mono<? extends @Nullable Void> before2(Flux<@Nullable Void> flux) {
return flux.ignoreElements();
}
@AfterTemplate
Mono<@Nullable Void> after(Flux<T> flux) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> after(Flux<T> flux) {
return flux.then();
}
}
@@ -980,12 +996,14 @@ final class ReactorRules {
/** Avoid vacuous invocations of {@link Mono#ignoreElement()}. */
static final class MonoThenEmpty<T> {
@BeforeTemplate
Mono<@Nullable Void> before(Mono<T> mono, Publisher<@Nullable Void> publisher) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before(Mono<T> mono, Publisher<@Nullable Void> publisher) {
return mono.ignoreElement().thenEmpty(publisher);
}
@AfterTemplate
Mono<@Nullable Void> after(Mono<T> mono, Publisher<@Nullable Void> publisher) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> after(Mono<T> mono, Publisher<@Nullable Void> publisher) {
return mono.thenEmpty(publisher);
}
}
@@ -993,12 +1011,14 @@ final class ReactorRules {
/** Avoid vacuous invocations of {@link Flux#ignoreElements()}. */
static final class FluxThenEmpty<T> {
@BeforeTemplate
Mono<@Nullable Void> before(Flux<T> flux, Publisher<@Nullable Void> publisher) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before(Flux<T> flux, Publisher<@Nullable Void> publisher) {
return flux.ignoreElements().thenEmpty(publisher);
}
@AfterTemplate
Mono<@Nullable Void> after(Flux<T> flux, Publisher<@Nullable Void> publisher) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> after(Flux<T> flux, Publisher<@Nullable Void> publisher) {
return flux.thenEmpty(publisher);
}
}
@@ -1054,7 +1074,8 @@ final class ReactorRules {
}
@BeforeTemplate
Mono<@Nullable Void> before2(Mono<T> mono1, Mono<@Nullable Void> mono2) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before2(Mono<T> mono1, Mono<@Nullable Void> mono2) {
return mono1.thenEmpty(mono2);
}
@@ -1072,7 +1093,8 @@ final class ReactorRules {
}
@BeforeTemplate
Mono<@Nullable Void> before2(Flux<T> flux, Mono<@Nullable Void> mono) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before2(Flux<T> flux, Mono<@Nullable Void> mono) {
return flux.thenEmpty(mono);
}

View File

@@ -23,14 +23,16 @@ final class RxJava2AdapterRules {
/** Use the fluent API style when using {@link RxJava2Adapter#completableToMono}. */
static final class CompletableToMono {
@BeforeTemplate
Mono<@Nullable Void> before(Completable completable) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> before(Completable completable) {
return Refaster.anyOf(
RxJava2Adapter.completableToMono(completable),
completable.to(RxJava2Adapter::completableToMono));
}
@AfterTemplate
Mono<@Nullable Void> after(Completable completable) {
@SuppressWarnings("java:S4968" /* Result may be `Mono<Void>`. */)
Mono<? extends @Nullable Void> after(Completable completable) {
return completable.as(RxJava2Adapter::completableToMono);
}
}

View File

@@ -16,6 +16,8 @@ final class ExplicitArgumentEnumerationTest {
"import com.google.common.collect.ImmutableList;",
"import com.google.errorprone.CompilationTestHelper;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import io.micrometer.core.instrument.Counter;",
"import io.micrometer.core.instrument.Tag;",
"import org.jooq.impl.DSL;",
"import reactor.core.publisher.Flux;",
"import reactor.test.StepVerifier;",
@@ -32,6 +34,8 @@ final class ExplicitArgumentEnumerationTest {
"",
" DSL.row(ImmutableList.of(1, 2));",
"",
" Counter.builder(\"foo\").tags(ImmutableList.of(Tag.of(\"bar\", \"baz\")));",
"",
" // BUG: Diagnostic contains:",
" unaryMethod(ImmutableList.of(1, 2));",
" unaryMethodWithLessVisibleOverload(ImmutableList.of(1, 2));",

View File

@@ -67,10 +67,11 @@ final class AssertJEnumerableRulesTest implements RefasterRuleCollectionTestCase
assertThat(ImmutableSet.of(4)).size().isBetween(5, 6));
}
ImmutableSet<EnumerableAssert<?, Integer>> testEnumerableAssertHasSameSizeAs() {
ImmutableSet<EnumerableAssert<?, Character>> testEnumerableAssertHasSameSizeAs() {
return ImmutableSet.of(
assertThat(ImmutableSet.of(1)).hasSize(Iterables.size(ImmutableSet.of(2))),
assertThat(ImmutableSet.of(3)).hasSize(ImmutableSet.of(4).size()),
assertThat(ImmutableSet.of(5)).hasSize(new Integer[0].length));
assertThat("foo").hasSize(Iterables.size(ImmutableSet.of(1))),
assertThat("bar").hasSize(ImmutableSet.of(2).size()),
assertThat("baz").hasSize(new Integer[0].length),
assertThat("qux").hasSize("quux".length()));
}
}

View File

@@ -66,10 +66,11 @@ final class AssertJEnumerableRulesTest implements RefasterRuleCollectionTestCase
assertThat(ImmutableSet.of(4)).hasSizeBetween(5, 6));
}
ImmutableSet<EnumerableAssert<?, Integer>> testEnumerableAssertHasSameSizeAs() {
ImmutableSet<EnumerableAssert<?, Character>> testEnumerableAssertHasSameSizeAs() {
return ImmutableSet.of(
assertThat(ImmutableSet.of(1)).hasSameSizeAs(ImmutableSet.of(2)),
assertThat(ImmutableSet.of(3)).hasSameSizeAs(ImmutableSet.of(4)),
assertThat(ImmutableSet.of(5)).hasSameSizeAs(new Integer[0]));
assertThat("foo").hasSameSizeAs(ImmutableSet.of(1)),
assertThat("bar").hasSameSizeAs(ImmutableSet.of(2)),
assertThat("baz").hasSameSizeAs(new Integer[0]),
assertThat("qux").hasSameSizeAs("quux"));
}
}

View File

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

View File

@@ -29,6 +29,7 @@ import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Type;
import java.util.List;
import java.util.Optional;
@@ -126,7 +127,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
return Optional.empty();
}
Symbol sym = ASTHelpers.getSymbol(methodSelect);
Symbol sym = ASTHelpers.getSymbol(subTree);
return ASTHelpers.isStatic(sym)
? constructFix(lambdaExpr, sym.owner, methodSelect)
: constructFix(lambdaExpr, "this", methodSelect);
@@ -200,7 +201,8 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
Name sName = target.getSimpleName();
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
if (!"java.lang".equals(ASTHelpers.enclosingPackage(target).toString())) {
PackageSymbol pkg = ASTHelpers.enclosingPackage(target);
if (pkg != null && !"java.lang".equals(pkg.toString())) {
Name fqName = target.getQualifiedName();
if (!sName.equals(fqName)) {
return fix.map(b -> b.addImport(fqName.toString()));

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.21.1-SNAPSHOT</version>
<version>0.22.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-guidelines</artifactId>
@@ -118,9 +118,6 @@
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>

View File

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

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.utils;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
@@ -115,9 +117,15 @@ public final class AnnotationAttributeMatcher implements Serializable {
}
private static String extractAttributeName(ExpressionTree expr) {
return (expr instanceof AssignmentTree assignment)
? ASTHelpers.getSymbol(assignment.getVariable()).getSimpleName().toString()
: "value";
if (!(expr instanceof AssignmentTree assignment)) {
return "value";
}
return requireNonNull(
ASTHelpers.getSymbol(assignment.getVariable()),
"Missing symbol for annotation attribute")
.getSimpleName()
.toString();
}
// XXX: The caller of this method can be implemented more efficiently in case of a "wholeTypes"

View File

@@ -11,6 +11,8 @@ additional_source_directories='${project.basedir}${file.separator}src${file.sepa
shared_error_prone_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).*'
patch_error_prone_flags=''
validation_error_prone_flags=''
patch_error_prone_flags='-XepOpt:Refaster:NamePattern=.*Workshop.*"'
validation_error_prone_flags='-XepOpt:Refaster:NamePattern=.*Workshop.*"'
# 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.

View File

@@ -38,7 +38,7 @@
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>33.4.0-jre<!-- Renovate: com.google.guava:guava-bom --></version>
+ <version>33.4.7-jre<!-- Renovate: com.google.guava:guava-bom --></version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>

308
mvnw vendored Executable file
View File

@@ -0,0 +1,308 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

205
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,205 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

251
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.21.1-SNAPSHOT</version>
<version>0.22.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -48,6 +48,7 @@
<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">
@@ -146,7 +147,7 @@
<error-prone.self-check-args />
<!-- The build timestamp is derived from the most recent commit
timestamp in support of reproducible builds. -->
<project.build.outputTimestamp>2025-03-24T10:41:07Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2025-04-14T09:36:15Z</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
@@ -210,16 +211,12 @@
<version.error-prone-slf4j>0.1.28</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>17</version.jdk>
<version.jmh>1.37</version.jmh>
<version.maven>3.9.9</version.maven>
<version.mockito>5.17.0</version.mockito>
<!-- XXX: Consider providing our own implementation with similar
functionality, and designing it such that JMH classes would not need to
be annotated `@Open`. -->
<version.nopen>1.0.1</version.nopen>
<version.nullaway>0.12.5</version.nullaway>
<version.pitest-git>2.2.0</version.pitest-git>
<version.rewrite-templating>1.24.2</version.rewrite-templating>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.12.6</version.nullaway>
<version.pitest-git>2.2.1</version.pitest-git>
<version.rewrite-templating>1.25.1</version.rewrite-templating>
<version.surefire>3.2.3</version.surefire>
</properties>
@@ -307,6 +304,16 @@
<artifactId>auto-value-annotations</artifactId>
<version>${version.auto-value}</version>
</dependency>
<!-- XXX: JSR-305 (meta-)annotation usage by some dependencies
triggers NullAway to attempt to load said annotations. As such
some modules require these annotations to be on the classpath.
Periodically review whether we can drop this dependeny version
declaration. See https://github.com/uber/NullAway/issues/1171. -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotation</artifactId>
@@ -345,7 +352,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>33.4.0-jre</version>
<version>33.4.7-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -354,15 +361,10 @@
<artifactId>truth</artifactId>
<version>1.4.4</version>
</dependency>
<dependency>
<groupId>com.jakewharton.nopen</groupId>
<artifactId>nopen-annotations</artifactId>
<version>${version.nopen}</version>
</dependency>
<dependency>
<groupId>com.jakewharton.nopen</groupId>
<artifactId>nopen-checker</artifactId>
<version>${version.nopen}</version>
<version>${version.nopen-checker}</version>
</dependency>
<dependency>
<groupId>com.uber.nullaway</groupId>
@@ -372,7 +374,7 @@
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-bom</artifactId>
<version>1.14.5</version>
<version>1.14.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -396,7 +398,7 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.29</version>
<version>2.2.30</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -433,18 +435,10 @@
<artifactId>byte-buddy</artifactId>
<version>1.17.5</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>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.23</version>
<version>1.9.24</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
@@ -456,7 +450,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.49.1</version>
<version>3.49.2</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -481,7 +475,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.12.1</version>
<version>5.12.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -499,16 +493,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${version.jmh}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${version.jmh}</version>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-templating</artifactId>
@@ -517,7 +501,7 @@
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-recipe-bom</artifactId>
<version>3.5.0</version>
<version>3.6.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -633,6 +617,7 @@
<configuration>
<bundledSignatures>
<bundledSignature>jdk-internal</bundledSignature>
<bundledSignature>jdk-reflection</bundledSignature>
<bundledSignature>jdk-system-out</bundledSignature>
<!-- Other bundles are available but currently not
enabled:
@@ -642,10 +627,6 @@
- jdk-deprecated: we compile with `-Xlint:all`,
which causes the build to fail when _any_
deprecated method is called.
- jdk-reflection: this bundle should probably be
enabled, but currently
`java.lang.reflect.AccessibleObject#setAccessible`
is still called in various places.
- jdk-non-portable: the Error Prone integration
crucially relies on some of these APIs.
- jdk-unsafe: see
@@ -947,7 +928,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.22.0</version>
<version>10.23.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -983,11 +964,6 @@
<artifactId>auto-value</artifactId>
<version>${version.auto-value}</version>
</path>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${version.auto-value}</version>
</path>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
@@ -998,10 +974,6 @@
<artifactId>rewrite-templating</artifactId>
<version>${version.rewrite-templating}</version>
</path>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
@@ -1155,9 +1127,6 @@
<requireMatchingCoordinates>
<moduleNameMustMatchArtifactId>true</moduleNameMustMatchArtifactId>
</requireMatchingCoordinates>
<requireMavenVersion>
<version>${version.maven}</version>
</requireMavenVersion>
<requireNoRepositories />
<requirePluginVersions />
<requireUpperBoundDeps />
@@ -1345,11 +1314,6 @@
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
@@ -1447,7 +1411,6 @@
GPL-2.0-with-classpath-exception
| CDDL/GPLv2+CE
| CDDL + GPLv2 with classpath exception
| GNU General Public License (GPL), version 2, with the 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
@@ -1500,37 +1463,6 @@
<updateBuildOutputTimestampPolicy>never</updateBuildOutputTimestampPolicy>
</configuration>
</plugin>
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<exclusionPatterns>
<!-- The plugin suggests replacing usages of
Guava's `Iterables` class with
`java.util.stream.Stream` equivalents, but the
alternative is often more verbose, requiring
unnecessary conversions to and from streams. -->
<exclusionPattern>com/google/common/collect/Iterables\..*</exclusionPattern>
</exclusionPatterns>
<failOnViolations>false</failOnViolations>
<ignorePackages>
<!-- Some Refaster rules purposefully use outdated
patterns in their `@BeforeTemplate` methods. -->
<ignorePackage>tech.picnic.errorprone.refasterrules</ignorePackage>
</ignorePackages>
<javaVersion>${version.jdk}</javaVersion>
</configuration>
<executions>
<execution>
<id>run-modernizer</id>
<goals>
<goal>modernizer</goal>
</goals>
<phase>process-test-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
@@ -1543,28 +1475,10 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<failOnError>false</failOnError>
<release>false</release>
</configuration>
<executions>
<execution>
<id>check-maven-central-compliance</id>
<goals>
<goal>check-maven-central</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.19.0</version>
<version>1.19.1</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
@@ -1739,11 +1653,6 @@
profile. Necessary for dealing with plugins that default to failing
the build upon encountering a violation. -->
<id>avoid-errors</id>
<activation>
<property>
<name>verification.warn</name>
</property>
</activation>
<build>
<pluginManagement>
<plugins>
@@ -1764,11 +1673,6 @@
that they can be disabled during development. See also the
`error-prone` profile defined below. -->
<id>build-checks</id>
<activation>
<property>
<name>!verification.skip</name>
</property>
</activation>
<build>
<plugins>
<plugin>
@@ -1860,14 +1764,6 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>tidy-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
@@ -1905,7 +1801,7 @@
<path>
<groupId>com.jakewharton.nopen</groupId>
<artifactId>nopen-checker</artifactId>
<version>${version.nopen}</version>
<version>${version.nopen-checker}</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
@@ -1977,6 +1873,7 @@
-XepOpt:NullAway:AnnotatedPackages=tech.picnic
-XepOpt:NullAway:AssertsEnabled=true
-XepOpt:NullAway:CheckOptionalEmptiness=true
-XepOpt:NullAway:JSpecifyMode=true
-XepOpt:Nullness:Conservative=false
-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true
<!-- Append additional custom arguments. -->
@@ -2023,11 +1920,6 @@
This profile allows one to collect all violations without failing
the build. -->
<id>disallow-warnings</id>
<activation>
<property>
<name>!verification.warn</name>
</property>
</activation>
<build>
<pluginManagement>
<plugins>
@@ -2096,21 +1988,6 @@
<failOnMissing>true</failOnMissing>
</configuration>
</plugin>
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<configuration>
<failOnViolations>true</failOnViolations>
</configuration>
</plugin>
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
<configuration>
<failOnError>true</failOnError>
<failOnWarning>true</failOnWarning>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
@@ -2182,74 +2059,6 @@
<artifactId>maven-gpg-plugin</artifactId>
</plugin>
<?SORTPOM RESUME?>
<plugin>
<groupId>org.kordamp.maven</groupId>
<artifactId>pomchecker-maven-plugin</artifactId>
<configuration>
<release>true</release>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!-- Enables execution of a JMH benchmark. Given a benchmark class
`tech.picnic.MyBenchmark`, the following command (executed against
the (sub)module in which the benchmark resides) will compile and
execute said benchmark:
mvn process-test-classes -Dverification.skip \
-Djmh.run-benchmark=tech.picnic.MyBenchmark
-->
<id>run-jmh-benchmark</id>
<activation>
<property>
<name>jmh.run-benchmark</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>build-jmh-runtime-classpath</id>
<goals>
<goal>build-classpath</goal>
</goals>
<configuration>
<outputProperty>testClasspath</outputProperty>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>run-jmh-benchmark</id>
<goals>
<goal>java</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<classpathScope>test</classpathScope>
<mainClass>${jmh.run-benchmark}</mainClass>
<systemProperties>
<!-- The runtime classpath is defined
in this way so that any JVMs forked by
JMH will have the desired classpath. -->
<systemProperty>
<key>java.class.path</key>
<value>${project.build.testOutputDirectory}${path.separator}${project.build.outputDirectory}${path.separator}${testClasspath}</value>
</systemProperty>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.21.1-SNAPSHOT</version>
<version>0.22.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-runner</artifactId>
@@ -26,21 +26,13 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
<scope>provided</scope>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotation</artifactId>
@@ -56,11 +48,6 @@
<artifactId>error_prone_check_api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_test_helpers</artifactId>
@@ -71,11 +58,6 @@
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.jakewharton.nopen</groupId>
<artifactId>nopen-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
@@ -113,11 +95,6 @@
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openrewrite</groupId>
<artifactId>rewrite-core</artifactId>

View File

@@ -1,126 +0,0 @@
package tech.picnic.errorprone.refaster.runner;
import static java.util.Comparator.comparingInt;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A node in an immutable tree.
*
* <p>The tree's edges are string-labeled, while its leaves store values of type {@code T}.
*/
@AutoValue
abstract class Node<T> {
// XXX: Review: should this method accept a `SetMultimap<V, ? extends Set<String>>`, or should
// there be such an overload?
static <T> Node<T> create(
Set<T> values, Function<? super T, ? extends Set<? extends Set<String>>> pathExtractor) {
Builder<T> tree = Builder.create();
tree.register(values, pathExtractor);
return tree.build();
}
abstract ImmutableMap<String, Node<T>> children();
abstract ImmutableList<T> values();
// XXX: Consider having `RefasterRuleSelector` already collect the candidate edges into a
// `SortedSet`, as that would likely speed up `ImmutableSortedSet#copyOf`.
// XXX: If this ^ proves worthwhile, then the test code and benchmark should be updated
// accordingly.
void collectReachableValues(Set<String> candidateEdges, Consumer<T> sink) {
collectReachableValues(ImmutableSortedSet.copyOf(candidateEdges).asList(), sink);
}
private void collectReachableValues(ImmutableList<String> candidateEdges, Consumer<T> sink) {
values().forEach(sink);
if (candidateEdges.isEmpty() || children().isEmpty()) {
return;
}
/*
* For performance reasons we iterate over the smallest set of edges. In case there are fewer
* children than candidate edges we iterate over the former, at the cost of not pruning the set
* of candidate edges if a transition is made.
*/
int candidateEdgeCount = candidateEdges.size();
if (children().size() < candidateEdgeCount) {
for (Map.Entry<String, Node<T>> e : children().entrySet()) {
if (candidateEdges.contains(e.getKey())) {
e.getValue().collectReachableValues(candidateEdges, sink);
}
}
} else {
for (int i = 0; i < candidateEdgeCount; i++) {
Node<T> child = children().get(candidateEdges.get(i));
if (child != null) {
child.collectReachableValues(candidateEdges.subList(i + 1, candidateEdgeCount), sink);
}
}
}
}
@AutoValue
@SuppressWarnings("AutoValueImmutableFields" /* Type is used only during `Node` construction. */)
abstract static class Builder<T> {
private static <T> Builder<T> create() {
return new AutoValue_Node_Builder<>(new HashMap<>(), new ArrayList<>());
}
abstract Map<String, Builder<T>> children();
abstract List<T> values();
/**
* Registers all paths to each of the given values.
*
* <p>Shorter paths are registered first, so that longer paths can be skipped if a strict prefix
* leads to the same value.
*/
private void register(
Set<T> values, Function<? super T, ? extends Set<? extends Set<String>>> pathsExtractor) {
for (T value : values) {
List<? extends Set<String>> paths = new ArrayList<>(pathsExtractor.apply(value));
/*
* We sort paths by length ascending, so that in case of two paths where one is an initial
* prefix of the other, only the former is encoded (thus saving some space).
*/
paths.sort(comparingInt(Set::size));
paths.forEach(path -> registerPath(value, ImmutableList.sortedCopyOf(path)));
}
}
private void registerPath(T value, ImmutableList<String> path) {
if (values().contains(value)) {
/* Another (shorter) path already leads to this value. */
return;
}
if (path.isEmpty()) {
values().add(value);
} else {
children()
.computeIfAbsent(path.get(0), k -> create())
.registerPath(value, path.subList(1, path.size()));
}
}
private Node<T> build() {
return new AutoValue_Node<>(
ImmutableMap.copyOf(Maps.transformValues(children(), Builder::build)),
ImmutableList.copyOf(values()));
}
}
}

View File

@@ -21,6 +21,7 @@ import com.google.common.collect.TreeRangeSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.CodeTransformer;
import com.google.errorprone.CompositeCodeTransformer;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.ErrorProneOptions.Severity;
import com.google.errorprone.SubContext;
@@ -38,7 +39,6 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import javax.inject.Inject;
@@ -64,9 +64,8 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
private static final long serialVersionUID = 1L;
// XXX: Review this suppression.
@SuppressWarnings({"java:S1948", "serial"} /* Concrete instance will be `Serializable`. */)
private final RefasterRuleSelector ruleSelector;
private final CodeTransformer codeTransformer;
/** Instantiates a default {@link Refaster} instance. */
public Refaster() {
@@ -81,29 +80,16 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
@Inject
@VisibleForTesting
public Refaster(ErrorProneFlags flags) {
ruleSelector = createRefasterRuleSelector(flags);
codeTransformer = createCompositeCodeTransformer(flags);
}
@CanIgnoreReturnValue
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
Set<CodeTransformer> candidateTransformers = ruleSelector.selectCandidateRules(tree);
/* First, collect all matches. */
SubContext context = new SubContext(state.context);
List<Description> matches = new ArrayList<>();
for (CodeTransformer transformer : candidateTransformers) {
try {
transformer.apply(state.getPath(), context, matches::add);
} catch (LinkageError e) {
// XXX: This `try/catch` block handles the issue described and resolved in
// https://github.com/google/error-prone/pull/2456. Drop this block once that change is
// released.
// XXX: Find a way to identify that we're running Picnic's Error Prone fork and disable this
// fallback if so, as it might hide other bugs.
return Description.NO_MATCH;
}
}
codeTransformer.apply(state.getPath(), new SubContext(state.context), matches::add);
/* Then apply them. */
applyMatches(matches, ((JCCompilationUnit) tree).endPositions, state);
@@ -207,12 +193,10 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
return description.fixes.stream().flatMap(fix -> fix.getReplacements(endPositions).stream());
}
// XXX: Add a flag to disable the optimized `RefasterRuleSelector`. That would allow us to verify
// that we're not prematurely pruning rules.
private static RefasterRuleSelector createRefasterRuleSelector(ErrorProneFlags flags) {
private static CodeTransformer createCompositeCodeTransformer(ErrorProneFlags flags) {
ImmutableListMultimap<String, CodeTransformer> allTransformers =
CodeTransformers.getAllCodeTransformers();
return RefasterRuleSelector.create(
return CompositeCodeTransformer.compose(
flags
.get(INCLUDED_RULES_PATTERN_FLAG)
.map(Pattern::compile)

View File

@@ -1,510 +0,0 @@
package tech.picnic.errorprone.refaster.runner;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Collections.newSetFromMap;
import static java.util.stream.Collectors.toCollection;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.CodeTransformer;
import com.google.errorprone.CompositeCodeTransformer;
import com.google.errorprone.refaster.BlockTemplate;
import com.google.errorprone.refaster.ExpressionTemplate;
import com.google.errorprone.refaster.RefasterRule;
import com.google.errorprone.refaster.UAnyOf;
import com.google.errorprone.refaster.UExpression;
import com.google.errorprone.refaster.UStatement;
import com.google.errorprone.refaster.UStaticIdent;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.PackageTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.AnnotatedCompositeCodeTransformer;
// XXX: Add some examples of which source files would match what templates in the tree.
// XXX: Consider this text in general.
/**
* A {@link RefasterRuleSelector} algorithm that selects Refaster templates based on the content of
* a {@link CompilationUnitTree}.
*
* <p>The algorithm consists of the following steps:
*
* <ol>
* <li>Create a {@link Node tree} structure based on the provided Refaster templates.
* <ol>
* <li>Extract all identifiers from the {@link BeforeTemplate}s.
* <li>Sort identifiers lexicographically and collect into a set.
* <li>Add a path to the tree based on the sorted identifiers.
* </ol>
* <li>Extract all identifiers from the {@link CompilationUnitTree} and sort them
* lexicographically.
* <li>Traverse the tree based on the identifiers from the {@link CompilationUnitTree}. Every node
* can contain Refaster templates. Once a node is we found a candidate Refaster template that
* might match some code and will therefore be added to the list of candidates.
* </ol>
*
* <p>This is an example to explain the algorithm. Consider the templates with identifiers; {@code
* T1 = [A, B, C]}, {@code T2 = [B]}, and {@code T3 = [B, D]}. This will result in the following
* tree structure:
*
* <pre>{@code
* <root>
* ├── A
* │ └── B
* │ └── C -- T1
* └── B -- T2
* └── D -- T3
* }</pre>
*
* <p>The tree is traversed based on the identifiers in the {@link CompilationUnitTree}. When a node
* containing a template is reached, we can be certain that the identifiers from the {@link
* BeforeTemplate} are at least present in the {@link CompilationUnitTree}.
*
* <p>Since the identifiers are sorted, we can skip parts of the {@link Node tree} while we are
* traversing it. Instead of trying to match all Refaster templates against every expression in a
* {@link CompilationUnitTree} we now only matching a subset of the templates that at least have a
* chance of matching. As a result, the performance of Refaster increases significantly.
*/
final class RefasterRuleSelector {
private final Node<CodeTransformer> codeTransformers;
private RefasterRuleSelector(Node<CodeTransformer> codeTransformers) {
this.codeTransformers = codeTransformers;
}
/**
* Instantiates a new {@link RefasterRuleSelector} backed by the given {@link CodeTransformer}s.
*/
static RefasterRuleSelector create(ImmutableCollection<CodeTransformer> refasterRules) {
Map<CodeTransformer, ImmutableSet<ImmutableSet<String>>> ruleIdentifiersByTransformer =
indexRuleIdentifiers(refasterRules);
return new RefasterRuleSelector(
Node.create(ruleIdentifiersByTransformer.keySet(), ruleIdentifiersByTransformer::get));
}
/**
* Retrieves a set of Refaster templates that can possibly match based on a {@link
* CompilationUnitTree}.
*
* @param tree The {@link CompilationUnitTree} for which candidate Refaster templates are
* selected.
* @return Set of Refaster templates that can possibly match in the provided {@link
* CompilationUnitTree}.
*/
Set<CodeTransformer> selectCandidateRules(CompilationUnitTree tree) {
Set<CodeTransformer> candidateRules = newSetFromMap(new IdentityHashMap<>());
codeTransformers.collectReachableValues(extractSourceIdentifiers(tree), candidateRules::add);
return candidateRules;
}
private static Map<CodeTransformer, ImmutableSet<ImmutableSet<String>>> indexRuleIdentifiers(
ImmutableCollection<CodeTransformer> codeTransformers) {
IdentityHashMap<CodeTransformer, ImmutableSet<ImmutableSet<String>>> identifiers =
new IdentityHashMap<>();
for (CodeTransformer transformer : codeTransformers) {
collectRuleIdentifiers(transformer, identifiers);
}
return identifiers;
}
private static void collectRuleIdentifiers(
CodeTransformer codeTransformer,
Map<CodeTransformer, ImmutableSet<ImmutableSet<String>>> identifiers) {
if (codeTransformer instanceof CompositeCodeTransformer compositeCodeTransformer) {
for (CodeTransformer transformer : compositeCodeTransformer.transformers()) {
collectRuleIdentifiers(transformer, identifiers);
}
} else if (codeTransformer instanceof AnnotatedCompositeCodeTransformer annotatedTransformer) {
for (Map.Entry<CodeTransformer, ImmutableSet<ImmutableSet<String>>> e :
indexRuleIdentifiers(annotatedTransformer.transformers()).entrySet()) {
identifiers.put(annotatedTransformer.withTransformers(e.getKey()), e.getValue());
}
} else if (codeTransformer instanceof RefasterRule) {
identifiers.put(
codeTransformer, extractRuleIdentifiers((RefasterRule<?, ?>) codeTransformer));
} else {
/* Unrecognized `CodeTransformer` types are indexed such that they always apply. */
identifiers.put(codeTransformer, ImmutableSet.of(ImmutableSet.of()));
}
}
// XXX: Consider decomposing `RefasterRule`s such that each rule has exactly one
// `@BeforeTemplate`.
private static ImmutableSet<ImmutableSet<String>> extractRuleIdentifiers(
RefasterRule<?, ?> refasterRule) {
ImmutableSet.Builder<ImmutableSet<String>> results = ImmutableSet.builder();
for (Object template : RefasterIntrospection.getBeforeTemplates(refasterRule)) {
if (template instanceof ExpressionTemplate expressionTemplate) {
UExpression expr = RefasterIntrospection.getExpression(expressionTemplate);
results.addAll(extractRuleIdentifiers(ImmutableList.of(expr)));
} else if (template instanceof BlockTemplate blockTemplate) {
ImmutableList<UStatement> statements =
RefasterIntrospection.getTemplateStatements(blockTemplate);
results.addAll(extractRuleIdentifiers(statements));
} else {
throw new IllegalStateException(
String.format("Unexpected template type '%s'", template.getClass()));
}
}
return results.build();
}
// XXX: Consider interning the strings (once a benchmark is in place).
private static ImmutableSet<ImmutableSet<String>> extractRuleIdentifiers(
ImmutableList<? extends Tree> trees) {
List<Set<String>> identifierCombinations = new ArrayList<>();
identifierCombinations.add(new HashSet<>());
TemplateIdentifierExtractor.INSTANCE.scan(trees, identifierCombinations);
return identifierCombinations.stream().map(ImmutableSet::copyOf).collect(toImmutableSet());
}
private static Set<String> extractSourceIdentifiers(Tree tree) {
Set<String> identifiers = new HashSet<>();
SourceIdentifierExtractor.INSTANCE.scan(tree, identifiers);
return identifiers;
}
/**
* Returns a unique string representation of the given {@link Tree.Kind}.
*
* @return A string representation of the operator, if known
* @throws IllegalArgumentException If the given input is not supported.
*/
// XXX: Extend list to cover remaining cases; at least for any `Kind` that may appear in a
// Refaster template. (E.g. keywords such as `if`, `instanceof`, `new`, ...)
private static String treeKindToString(Tree.Kind kind) {
return switch (kind) {
case ASSIGNMENT -> "=";
case POSTFIX_INCREMENT -> "x++";
case PREFIX_INCREMENT -> "++x";
case POSTFIX_DECREMENT -> "x--";
case PREFIX_DECREMENT -> "--x";
case UNARY_PLUS -> "+x";
case UNARY_MINUS -> "-x";
case BITWISE_COMPLEMENT -> "~";
case LOGICAL_COMPLEMENT -> "!";
case MULTIPLY -> "*";
case DIVIDE -> "/";
case REMAINDER -> "%";
case PLUS -> "+";
case MINUS -> "-";
case LEFT_SHIFT -> "<<";
case RIGHT_SHIFT -> ">>";
case UNSIGNED_RIGHT_SHIFT -> ">>>";
case LESS_THAN -> "<";
case GREATER_THAN -> ">";
case LESS_THAN_EQUAL -> "<=";
case GREATER_THAN_EQUAL -> ">=";
case EQUAL_TO -> "==";
case NOT_EQUAL_TO -> "!=";
case AND -> "&";
case XOR -> "^";
case OR -> "|";
case CONDITIONAL_AND -> "&&";
case CONDITIONAL_OR -> "||";
case MULTIPLY_ASSIGNMENT -> "*=";
case DIVIDE_ASSIGNMENT -> "/=";
case REMAINDER_ASSIGNMENT -> "%=";
case PLUS_ASSIGNMENT -> "+=";
case MINUS_ASSIGNMENT -> "-=";
case LEFT_SHIFT_ASSIGNMENT -> "<<=";
case RIGHT_SHIFT_ASSIGNMENT -> ">>=";
case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT -> ">>>=";
case AND_ASSIGNMENT -> "&=";
case XOR_ASSIGNMENT -> "^=";
case OR_ASSIGNMENT -> "|=";
default -> throw new IllegalStateException("Cannot convert Tree.Kind to a String: " + kind);
};
}
private static final class RefasterIntrospection {
// XXX: Update `ErrorProneRuntimeClasspath` to not suggest inaccessible types.
@SuppressWarnings("ErrorProneRuntimeClasspath")
private static final String UCLASS_IDENT_FQCN = "com.google.errorprone.refaster.UClassIdent";
private static final Class<?> UCLASS_IDENT = getClass(UCLASS_IDENT_FQCN);
private static final Method METHOD_REFASTER_RULE_BEFORE_TEMPLATES =
getMethod(RefasterRule.class, "beforeTemplates");
private static final Method METHOD_EXPRESSION_TEMPLATE_EXPRESSION =
getMethod(ExpressionTemplate.class, "expression");
private static final Method METHOD_BLOCK_TEMPLATE_TEMPLATE_STATEMENTS =
getMethod(BlockTemplate.class, "templateStatements");
private static final Method METHOD_USTATIC_IDENT_CLASS_IDENT =
getMethod(UStaticIdent.class, "classIdent");
private static final Method METHOD_UCLASS_IDENT_GET_TOP_LEVEL_CLASS =
getMethod(UCLASS_IDENT, "getTopLevelClass");
private static final Method METHOD_UANY_OF_EXPRESSIONS = getMethod(UAnyOf.class, "expressions");
static boolean isUClassIdent(IdentifierTree tree) {
return UCLASS_IDENT.isInstance(tree);
}
static ImmutableList<?> getBeforeTemplates(RefasterRule<?, ?> refasterRule) {
return invokeMethod(METHOD_REFASTER_RULE_BEFORE_TEMPLATES, refasterRule);
}
static UExpression getExpression(ExpressionTemplate template) {
return invokeMethod(METHOD_EXPRESSION_TEMPLATE_EXPRESSION, template);
}
static ImmutableList<UStatement> getTemplateStatements(BlockTemplate template) {
return invokeMethod(METHOD_BLOCK_TEMPLATE_TEMPLATE_STATEMENTS, template);
}
static IdentifierTree getClassIdent(UStaticIdent tree) {
return invokeMethod(METHOD_USTATIC_IDENT_CLASS_IDENT, tree);
}
// Arguments to this method must actually be of the package-private type `UClassIdent`.
static String getTopLevelClass(IdentifierTree uClassIdent) {
return invokeMethod(METHOD_UCLASS_IDENT_GET_TOP_LEVEL_CLASS, uClassIdent);
}
static ImmutableList<UExpression> getExpressions(UAnyOf tree) {
return invokeMethod(METHOD_UANY_OF_EXPRESSIONS, tree);
}
private static Class<?> getClass(String fqcn) {
try {
return RefasterIntrospection.class.getClassLoader().loadClass(fqcn);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format("Failed to load class `%s`", fqcn), e);
}
}
private static Method getMethod(Class<?> clazz, String methodName) {
try {
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
String.format("No method `%s` on class `%s`", methodName, clazz.getName()), e);
}
}
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
private static <T> T invokeMethod(Method method, Object instance) {
try {
return (T) method.invoke(instance);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(String.format("Failed to invoke method `%s`", method), e);
}
}
}
private static final class TemplateIdentifierExtractor
extends TreeScanner<@Nullable Void, List<Set<String>>> {
private static final TemplateIdentifierExtractor INSTANCE = new TemplateIdentifierExtractor();
@Override
public @Nullable Void visitIdentifier(
IdentifierTree node, List<Set<String>> identifierCombinations) {
// XXX: Also include the package name if not `java.lang`; it must be present.
if (RefasterIntrospection.isUClassIdent(node)) {
for (Set<String> ids : identifierCombinations) {
ids.add(getSimpleName(RefasterIntrospection.getTopLevelClass(node)));
ids.add(getIdentifier(node));
}
} else if (node instanceof UStaticIdent uStaticIdent) {
IdentifierTree subNode = RefasterIntrospection.getClassIdent(uStaticIdent);
for (Set<String> ids : identifierCombinations) {
ids.add(getSimpleName(RefasterIntrospection.getTopLevelClass(subNode)));
ids.add(getIdentifier(subNode));
ids.add(node.getName().toString());
}
}
return null;
}
private static String getIdentifier(IdentifierTree tree) {
return getSimpleName(tree.getName().toString());
}
private static String getSimpleName(String fqcn) {
int index = fqcn.lastIndexOf('.');
return index < 0 ? fqcn : fqcn.substring(index + 1);
}
@Override
public @Nullable Void visitMemberReference(
MemberReferenceTree node, List<Set<String>> identifierCombinations) {
super.visitMemberReference(node, identifierCombinations);
String id = node.getName().toString();
identifierCombinations.forEach(ids -> ids.add(id));
return null;
}
@Override
public @Nullable Void visitMemberSelect(
MemberSelectTree node, List<Set<String>> identifierCombinations) {
super.visitMemberSelect(node, identifierCombinations);
String id = node.getIdentifier().toString();
identifierCombinations.forEach(ids -> ids.add(id));
return null;
}
@Override
public @Nullable Void visitAssignment(
AssignmentTree node, List<Set<String>> identifierCombinations) {
registerOperator(node, identifierCombinations);
return super.visitAssignment(node, identifierCombinations);
}
@Override
public @Nullable Void visitCompoundAssignment(
CompoundAssignmentTree node, List<Set<String>> identifierCombinations) {
registerOperator(node, identifierCombinations);
return super.visitCompoundAssignment(node, identifierCombinations);
}
@Override
public @Nullable Void visitUnary(UnaryTree node, List<Set<String>> identifierCombinations) {
registerOperator(node, identifierCombinations);
return super.visitUnary(node, identifierCombinations);
}
@Override
public @Nullable Void visitBinary(BinaryTree node, List<Set<String>> identifierCombinations) {
registerOperator(node, identifierCombinations);
return super.visitBinary(node, identifierCombinations);
}
private static void registerOperator(
ExpressionTree node, List<Set<String>> identifierCombinations) {
String id = treeKindToString(node.getKind());
identifierCombinations.forEach(ids -> ids.add(id));
}
@Override
public @Nullable Void visitOther(Tree node, List<Set<String>> identifierCombinations) {
if (node instanceof UAnyOf uAnyOf) {
List<Set<String>> base = copy(identifierCombinations);
identifierCombinations.clear();
for (UExpression expr : RefasterIntrospection.getExpressions(uAnyOf)) {
List<Set<String>> branch = copy(base);
scan(expr, branch);
identifierCombinations.addAll(branch);
}
}
return null;
}
private static List<Set<String>> copy(List<Set<String>> identifierCombinations) {
return identifierCombinations.stream()
.map(HashSet::new)
.collect(toCollection(ArrayList::new));
}
}
private static final class SourceIdentifierExtractor
extends TreeScanner<@Nullable Void, Set<String>> {
private static final SourceIdentifierExtractor INSTANCE = new SourceIdentifierExtractor();
@Override
public @Nullable Void visitPackage(PackageTree node, Set<String> identifiers) {
/* Refaster rules never match package declarations. */
return null;
}
@Override
public @Nullable Void visitClass(ClassTree node, Set<String> identifiers) {
/*
* Syntactic details of a class declaration other than the definition of its members do not
* need to be reflected in a Refaster rule for it to apply to the class's code.
*/
return scan(node.getMembers(), identifiers);
}
@Override
public @Nullable Void visitMethod(MethodTree node, Set<String> identifiers) {
/*
* Syntactic details of a method declaration other than its body do not need to be reflected
* in a Refaster rule for it to apply to the method's code.
*/
return scan(node.getBody(), identifiers);
}
@Override
public @Nullable Void visitVariable(VariableTree node, Set<String> identifiers) {
/* A variable's modifiers and name do not influence where a Refaster rule matches. */
return reduce(scan(node.getInitializer(), identifiers), scan(node.getType(), identifiers));
}
@Override
public @Nullable Void visitIdentifier(IdentifierTree node, Set<String> identifiers) {
identifiers.add(node.getName().toString());
return null;
}
@Override
public @Nullable Void visitMemberReference(MemberReferenceTree node, Set<String> identifiers) {
super.visitMemberReference(node, identifiers);
identifiers.add(node.getName().toString());
return null;
}
@Override
public @Nullable Void visitMemberSelect(MemberSelectTree node, Set<String> identifiers) {
super.visitMemberSelect(node, identifiers);
identifiers.add(node.getIdentifier().toString());
return null;
}
@Override
public @Nullable Void visitAssignment(AssignmentTree node, Set<String> identifiers) {
registerOperator(node, identifiers);
return super.visitAssignment(node, identifiers);
}
@Override
public @Nullable Void visitCompoundAssignment(
CompoundAssignmentTree node, Set<String> identifiers) {
registerOperator(node, identifiers);
return super.visitCompoundAssignment(node, identifiers);
}
@Override
public @Nullable Void visitUnary(UnaryTree node, Set<String> identifiers) {
registerOperator(node, identifiers);
return super.visitUnary(node, identifiers);
}
@Override
public @Nullable Void visitBinary(BinaryTree node, Set<String> identifiers) {
registerOperator(node, identifiers);
return super.visitBinary(node, identifiers);
}
private static void registerOperator(ExpressionTree node, Set<String> identifiers) {
identifiers.add(treeKindToString(node.getKind()));
}
}
}

View File

@@ -1,83 +0,0 @@
package tech.picnic.errorprone.refaster.runner;
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
import static java.util.function.Function.identity;
import com.google.common.collect.ImmutableListMultimap;
import com.jakewharton.nopen.annotation.Open;
import java.util.Collection;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import tech.picnic.errorprone.refaster.runner.NodeTestCase.NodeTestCaseEntry;
@Open
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(jvmArgs = {"-Xms1G", "-Xmx1G"})
@Warmup(iterations = 5)
@Measurement(iterations = 10)
public class NodeBenchmark {
@SuppressWarnings("NullAway" /* Initialized by `@Setup` method. */)
private ImmutableListMultimap<NodeTestCase<Integer>, NodeTestCaseEntry<Integer>> testCases;
public static void main(String[] args) throws RunnerException {
// XXX: Update `ErrorProneRuntimeClasspath` to allow same-package `Class` references.
@SuppressWarnings("ErrorProneRuntimeClasspath")
String testRegex = Pattern.quote(NodeBenchmark.class.getCanonicalName());
new Runner(new OptionsBuilder().include(testRegex).forks(1).build()).run();
}
@Setup
public final void setUp() {
Random random = new Random(0);
testCases =
Stream.of(
NodeTestCase.generate(100, 5, 10, 10, random),
NodeTestCase.generate(100, 5, 10, 100, random),
NodeTestCase.generate(100, 5, 10, 1000, random),
NodeTestCase.generate(1000, 10, 20, 10, random),
NodeTestCase.generate(1000, 10, 20, 100, random),
NodeTestCase.generate(1000, 10, 20, 1000, random),
NodeTestCase.generate(1000, 10, 20, 10000, random))
.collect(
flatteningToImmutableListMultimap(
identity(), testCase -> testCase.generateTestCaseEntries(random)));
}
@Benchmark
public final void create(Blackhole bh) {
for (NodeTestCase<Integer> testCase : testCases.keySet()) {
bh.consume(testCase.buildTree());
}
}
@Benchmark
public final void collectReachableValues(Blackhole bh) {
for (Map.Entry<NodeTestCase<Integer>, Collection<NodeTestCaseEntry<Integer>>> e :
testCases.asMap().entrySet()) {
Node<Integer> tree = e.getKey().buildTree();
for (NodeTestCaseEntry<Integer> testCaseEntry : e.getValue()) {
tree.collectReachableValues(testCaseEntry.candidateEdges(), bh::consume);
}
}
}
}

View File

@@ -1,47 +0,0 @@
package tech.picnic.errorprone.refaster.runner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
final class NodeTest {
private static Stream<Arguments> collectReachableValuesTestCases() {
Random random = new Random(0);
return Stream.of(
NodeTestCase.generate(0, 0, 0, 0, random),
NodeTestCase.generate(1, 1, 1, 1, random),
NodeTestCase.generate(2, 2, 2, 10, random),
NodeTestCase.generate(10, 2, 5, 10, random),
NodeTestCase.generate(10, 2, 5, 100, random),
NodeTestCase.generate(100, 5, 10, 100, random),
NodeTestCase.generate(100, 5, 10, 1000, random))
.flatMap(
testCase -> {
Node<Integer> tree = testCase.buildTree();
return testCase
.generateTestCaseEntries(random)
.map(e -> arguments(tree, e.candidateEdges(), e.reachableValues()));
});
}
@MethodSource("collectReachableValuesTestCases")
@ParameterizedTest
void collectReachableValues(
Node<Integer> tree,
ImmutableSet<String> candidateEdges,
Collection<Integer> expectedReachable) {
List<Integer> actualReachable = new ArrayList<>();
tree.collectReachableValues(candidateEdges, actualReachable::add);
assertThat(actualReachable).hasSameElementsAs(expectedReachable);
}
}

View File

@@ -1,140 +0,0 @@
package tech.picnic.errorprone.refaster.runner;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSetMultimap.flatteningToImmutableSetMultimap;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.collectingAndThen;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;
@AutoValue
abstract class NodeTestCase<V> {
static NodeTestCase<Integer> generate(
int entryCount, int maxPathCount, int maxPathLength, int pathValueDomainSize, Random random) {
return random
.ints(entryCount)
.boxed()
.collect(
collectingAndThen(
flatteningToImmutableSetMultimap(
identity(),
i ->
random
.ints(random.nextInt(maxPathCount + 1))
.mapToObj(
p ->
random
.ints(
random.nextInt(maxPathLength + 1),
0,
pathValueDomainSize)
.mapToObj(String::valueOf)
.collect(toImmutableSet()))),
AutoValue_NodeTestCase::new));
}
abstract ImmutableSetMultimap<V, ImmutableSet<String>> input();
final Node<V> buildTree() {
return Node.create(input().keySet(), input()::get);
}
final Stream<NodeTestCaseEntry<V>> generateTestCaseEntries(Random random) {
return generatePathTestCases(input(), random);
}
private static <V> Stream<NodeTestCaseEntry<V>> generatePathTestCases(
ImmutableSetMultimap<V, ImmutableSet<String>> treeInput, Random random) {
ImmutableSet<String> allEdges =
treeInput.values().stream().flatMap(ImmutableSet::stream).collect(toImmutableSet());
return Stream.concat(
Stream.of(ImmutableSet.<String>of()), shuffle(treeInput.values(), random).stream())
// XXX: Use `random.nextInt(20, 100)` once we no longer target JDK 11. (And consider
// introducing a Refaster template for this case.)
.limit(20 + random.nextInt(80))
.flatMap(edges -> generateVariations(edges, allEdges, "unused", random))
.distinct()
.map(edges -> createTestCaseEntry(treeInput, edges));
}
private static <T> Stream<ImmutableSet<T>> generateVariations(
ImmutableSet<T> baseEdges, ImmutableSet<T> allEdges, T unusedEdge, Random random) {
Optional<T> knownEdge = selectRandomElement(allEdges, random);
return Stream.of(
random.nextBoolean() ? null : baseEdges,
random.nextBoolean() ? null : shuffle(baseEdges, random),
random.nextBoolean() ? null : insertValue(baseEdges, unusedEdge, random),
baseEdges.isEmpty() || random.nextBoolean()
? null
: randomStrictSubset(baseEdges, random),
baseEdges.isEmpty() || random.nextBoolean()
? null
: insertValue(randomStrictSubset(baseEdges, random), unusedEdge, random),
baseEdges.isEmpty() || random.nextBoolean()
? null
: knownEdge
.map(edge -> insertValue(randomStrictSubset(baseEdges, random), edge, random))
.orElse(null))
.filter(Objects::nonNull);
}
private static <T> Optional<T> selectRandomElement(ImmutableSet<T> collection, Random random) {
return collection.isEmpty()
? Optional.empty()
: Optional.of(collection.asList().get(random.nextInt(collection.size())));
}
private static <T> ImmutableSet<T> shuffle(ImmutableCollection<T> values, Random random) {
List<T> allValues = new ArrayList<>(values);
Collections.shuffle(allValues, random);
return ImmutableSet.copyOf(allValues);
}
private static <T> ImmutableSet<T> insertValue(
ImmutableSet<T> values, T extraValue, Random random) {
List<T> allValues = new ArrayList<>(values);
allValues.add(random.nextInt(values.size() + 1), extraValue);
return ImmutableSet.copyOf(allValues);
}
private static <T> ImmutableSet<T> randomStrictSubset(ImmutableSet<T> values, Random random) {
checkArgument(!values.isEmpty(), "Cannot select strict subset of random collection");
List<T> allValues = new ArrayList<>(values);
Collections.shuffle(allValues, random);
return ImmutableSet.copyOf(allValues.subList(0, random.nextInt(allValues.size())));
}
private static <V> NodeTestCaseEntry<V> createTestCaseEntry(
ImmutableSetMultimap<V, ImmutableSet<String>> treeInput, ImmutableSet<String> edges) {
return new AutoValue_NodeTestCase_NodeTestCaseEntry<>(
edges,
treeInput.asMap().entrySet().stream()
.filter(e -> e.getValue().stream().anyMatch(edges::containsAll))
.map(Map.Entry::getKey)
.collect(toImmutableList()));
}
@AutoValue
abstract static class NodeTestCaseEntry<V> {
abstract ImmutableSet<String> candidateEdges();
abstract ImmutableList<V> reachableValues();
}
}

View File

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

View File

@@ -46,12 +46,7 @@ public abstract class AnnotatedCompositeCodeTransformer implements CodeTransform
abstract String packageName();
/**
* Return The {@link CodeTransformer}s to which to delegate.
*
* @return The ordered {@link CodeTransformer}s to which to delegate.
*/
public abstract ImmutableList<CodeTransformer> transformers();
abstract ImmutableList<CodeTransformer> transformers();
@Override
@SuppressWarnings("java:S3038" /* All AutoValue properties must be specified explicitly. */)
@@ -72,18 +67,6 @@ public abstract class AnnotatedCompositeCodeTransformer implements CodeTransform
return new AutoValue_AnnotatedCompositeCodeTransformer(packageName, transformers, annotations);
}
/**
* Returns a new {@link AnnotatedCompositeCodeTransformer} similar to this one, but with the
* specified transformers.
*
* @param transformers The replacement transformers.
* @return A derivative {@link AnnotatedCompositeCodeTransformer}.
*/
public AnnotatedCompositeCodeTransformer withTransformers(CodeTransformer... transformers) {
return new AutoValue_AnnotatedCompositeCodeTransformer(
packageName(), ImmutableList.copyOf(transformers), annotations());
}
@Override
public final void apply(TreePath path, Context context, DescriptionListener listener) {
for (CodeTransformer transformer : transformers()) {

View File

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

View File

@@ -1,31 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import static java.util.Collections.singletonMap;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map;
/** Refaster rule collection to validate the reporting of missing test methods. */
final class NestedRefasterAnyOfRules {
private NestedRefasterAnyOfRules() {}
static final class NestedRefasterAnyOf<K, V> {
@BeforeTemplate
@SuppressWarnings({"ImmutableMapOf1", "MapEntry"} /* Similar rules for testing purposes. */)
Map<K, V> before(K k1, V v1) {
return Refaster.anyOf(
ImmutableMap.ofEntries(
Refaster.anyOf(Map.entry(k1, v1), new SimpleImmutableEntry<>(k1, v1))),
singletonMap(k1, v1));
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1) {
return ImmutableMap.of(k1, v1);
}
}
}

View File

@@ -21,10 +21,8 @@ final class RefasterRuleCollectionTest {
MethodWithoutPrefixRules.class,
MisnamedTestClassRules.class,
MissingTestAndWrongTestRules.class,
NestedRefasterAnyOfRules.class,
PartialTestMatchRules.class,
RuleWithoutTestRules.class,
SingleRefasterAnyOfRules.class,
ValidRules.class
})
void verifyRefasterRuleCollections(Class<?> clazz) {

View File

@@ -1,27 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import static java.util.Collections.singletonMap;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Map;
/** Refaster rule collection to validate the reporting of missing test methods. */
final class SingleRefasterAnyOfRules {
private SingleRefasterAnyOfRules() {}
static final class SingleRefasterAnyOf<K, V> {
@BeforeTemplate
@SuppressWarnings("ImmutableMapOf1" /* Similar rule for testing purposes. */)
Map<K, V> before(K k1, V v1) {
return Refaster.anyOf(ImmutableMap.ofEntries(Map.entry(k1, v1)), singletonMap(k1, v1));
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1) {
return ImmutableMap.of(k1, v1);
}
}
}

View File

@@ -1,12 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
/** Code to test the Refaster rules from {@link NestedRefasterAnyOfRules}. */
final class NestedRefasterAnyOfRulesTest implements RefasterRuleCollectionTestCase {
Map<?, ?> testNestedRefasterAnyOf() {
Map<String, String> stringStringMap = Map.of("1", "2");
return ImmutableMap.ofEntries(Map.entry("k1", "v1"));
}
}

View File

@@ -1,12 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
/** Code to test the Refaster rules from {@link NestedRefasterAnyOfRules}. */
final class NestedRefasterAnyOfRulesTest implements RefasterRuleCollectionTestCase {
Map<?, ?> testNestedRefasterAnyOf() {
Map<String, String> stringStringMap = Map.of("1", "2");
return ImmutableMap.of("k1", "v1");
}
}

View File

@@ -1,17 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.Map;
/** Code to test the Refaster rules from {@link SingleRefasterAnyOfRules}. */
final class SingleRefasterAnyOfRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
Map<?, ?> testSingleRefasterAnyOf() {
return Collections.singletonMap("k1", "v1");
}
}

View File

@@ -1,18 +0,0 @@
package tech.picnic.errorprone.refaster.test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.Map;
/** Code to test the Refaster rules from {@link SingleRefasterAnyOfRules}. */
final class SingleRefasterAnyOfRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<Object> elidedTypesAndStaticImports() {
return ImmutableSet.of(Collections.class);
}
Map<?, ?> testSingleRefasterAnyOf() {
return ImmutableMap.of("k1", "v1");
}
}

View File

@@ -1,6 +1,9 @@
# An overview of Error Prone Support releases, along with compatible Error
# Prone releases. This data was generated by `generate-version-compatibility-overview.sh`.
releases:
- version: 0.22.0
compatible:
- "2.37.0"
- version: 0.21.0
compatible:
- "2.37.0"

104
workshop/README.md Normal file
View File

@@ -0,0 +1,104 @@
# Error Prone Workshop
Download the slides [here][eps-workshop-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 _File -> Settings... -> Build, Execution, Deployment ->
Compiler -> Java Compiler_ and deselect the option _Use '--release' option for
cross-compilation (Java 9 and later)_.
If you encounter any other bugs when running `mvn clean install`, make sure you
are using a recent release of JDK 17.
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 navigate to the
`tech.picnic.errorprone.workshop.refasterrules` package. There you can find one
example and five different exercises to do. Make sure to open the
`WorkshopRefasterRulesTest` class where you can enable tests. Per assignment
there is a test in this class that you 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 explain what needs to happen.
* Find the test case 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.
### Tips and tricks
If you are working from the command line, it is suggested to run commands in
the `workshop` directory. To do this, navigate to the root of the repository
and run:
```bash
cd workshop
```
In case you want to do a fast build to just compile the project, run:
```bash
mvn clean install -DskipTests -Dverification.skip
```
In case you want to run one test from the command line, run the following
commands:
```bash
mvn clean install -Dtest=WorkshopRefasterRulesTest -Dverification.skip
```
### Troubleshooting
Make sure to use the latest version of JDK 21 or 17.
In some specific cases the `-Dverification.skip` is not correctly processed. In
that case, use `-D"verification.skip"`.
In case there is an error mentioning the `BetaApi` check, please rebuild or try
to `Reload All Maven projects`.
[eps-github]: https://github.com/PicnicSupermarket/error-prone-support
[eps-workshop-slides]: https://docs.google.com/presentation/d/16MnDk6eW5dxKrLfG7TNUKAX-UVDJwbMz55UvKUkzeHc/edit?usp=sharing

155
workshop/pom.xml Normal file
View File

@@ -0,0 +1,155 @@
<?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.22.1-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>com.google.errorprone</groupId>
<artifactId>error_prone_annotation</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_check_api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</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>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<scope>provided</scope>
</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
boolean before(String str) {
return str.length() == 0;
}
@AfterTemplate
boolean after(String str) {
return str.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,18 @@
package tech.picnic.errorprone.workshop.refasterrules;
import com.google.common.collect.ImmutableList;
/** Refaster rules for the second assignment of the workshop. */
@SuppressWarnings("UnusedTypeParameter" /* Ignore this for demo purposes. */)
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> {
// XXX: Implement this Refaster rule, preferably by using only one `@BeforeTemplate`.
// Tip: use the type argument.
}
}

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 Assignment0DeleteEmptyMethodTest {
@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,42 @@
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);
}
@Disabled("Needs to be implemented in `WorkshopAssignment1Rules.java`.")
@Test
void validateFirstWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment1Rules.class);
}
@Disabled("Needs to be implemented in `WorkshopAssignment2Rules.java`.")
@Test
void validateSecondWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment2Rules.class);
}
@Disabled("Needs to be implemented in `WorkshopAssignment3Rules.java`.")
@Test
void validateThirdWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment3Rules.class);
}
@Disabled("Needs to be implemented in `WorkshopAssignment4Rules.java`.")
@Test
void validateFourthWorkshopAssignment() {
RefasterRuleCollection.validate(WorkshopAssignment4Rules.class);
}
@Disabled("Needs to be implemented in `WorkshopAssignment5Rules.java`.")
@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,25 @@
package tech.picnic.errorprone.workshop.refasterrules;
import java.util.concurrent.ThreadLocalRandom;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException();
}
if (!ThreadLocalRandom.current().nextBoolean()) {
throw new IllegalArgumentException();
}
}
void testCheckArgumentWithMessage() {
if (!"foo".isEmpty()) {
throw new IllegalArgumentException("The string is not empty");
}
if (!ThreadLocalRandom.current().nextBoolean()) {
throw new IllegalArgumentException(
"The rule should be able rewrite all kinds of messages ;).");
}
}
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.workshop.refasterrules;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.concurrent.ThreadLocalRandom;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class WorkshopAssignment3RulesTest implements RefasterRuleCollectionTestCase {
void testCheckArgumentWithoutMessage() {
checkArgument("foo".isEmpty());
checkArgument(ThreadLocalRandom.current().nextBoolean());
}
void testCheckArgumentWithMessage() {
checkArgument("foo".isEmpty(), "The string is not empty");
checkArgument(
ThreadLocalRandom.current().nextBoolean(),
"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"));
}
}