mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
63 Commits
rossendrij
...
nkooij/tim
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ced020ba6 | ||
|
|
389928ce43 | ||
|
|
d88f226b30 | ||
|
|
ba91c6bed7 | ||
|
|
85f402b089 | ||
|
|
ad8c0a472c | ||
|
|
0af127652e | ||
|
|
cede5e451b | ||
|
|
134895090f | ||
|
|
226bfd0cee | ||
|
|
89a3c605fe | ||
|
|
63273a2609 | ||
|
|
79768a2428 | ||
|
|
dd8d094b5a | ||
|
|
4830b5b2cd | ||
|
|
48772a044e | ||
|
|
281534aeca | ||
|
|
42e632e5db | ||
|
|
7febccb7ff | ||
|
|
d5c1c858d5 | ||
|
|
55d2622380 | ||
|
|
f10f2c9209 | ||
|
|
99f85614fd | ||
|
|
be24edadae | ||
|
|
068c03708b | ||
|
|
9c330981ea | ||
|
|
b780c05dc0 | ||
|
|
16955a9cfa | ||
|
|
8fa3ff3702 | ||
|
|
022a3d293e | ||
|
|
81227cdd94 | ||
|
|
04d886c031 | ||
|
|
afb2a28dcf | ||
|
|
dc0f90e981 | ||
|
|
2196bbd8f9 | ||
|
|
f3b81304b9 | ||
|
|
b0d374040a | ||
|
|
45dfc53d40 | ||
|
|
6cb10ffe2f | ||
|
|
92f2b0ab0f | ||
|
|
21388273c5 | ||
|
|
b2e15607c1 | ||
|
|
50e730fb40 | ||
|
|
a844b9e532 | ||
|
|
671ee1eedb | ||
|
|
7fe61c226a | ||
|
|
8fcc91febf | ||
|
|
e00aba12c3 | ||
|
|
0118cc6c10 | ||
|
|
91e009cab0 | ||
|
|
68dca3204e | ||
|
|
e6ccb523fd | ||
|
|
d0533f7190 | ||
|
|
759e7ea2ff | ||
|
|
2578857a37 | ||
|
|
1d28512f09 | ||
|
|
4efd79bfa6 | ||
|
|
d6b28733f6 | ||
|
|
b9884fa6d6 | ||
|
|
4fd8f57430 | ||
|
|
a2559bddda | ||
|
|
7de9bede19 | ||
|
|
85cb997f1b |
25
.github/workflows/build.yaml
vendored
25
.github/workflows/build.yaml
vendored
@@ -7,10 +7,27 @@ permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
jdk: [ 11.0.16, 17.0.4 ]
|
||||
os: [ ubuntu-22.04 ]
|
||||
jdk: [ 11.0.16, 17.0.4, 19 ]
|
||||
distribution: [ temurin ]
|
||||
experimental: [ false ]
|
||||
include:
|
||||
- os: macos-12
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: windows-2022
|
||||
jdk: 17.0.4
|
||||
distribution: temurin
|
||||
experimental: false
|
||||
- os: ubuntu-22.04
|
||||
jdk: 20-ea
|
||||
distribution: zulu
|
||||
experimental: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.experimental }}
|
||||
steps:
|
||||
# We run the build twice for each supported JDK: once against the
|
||||
# original Error Prone release, using only Error Prone checks available
|
||||
@@ -20,10 +37,10 @@ jobs:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3.5.1
|
||||
uses: actions/setup-java@v3.6.0
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
distribution: temurin
|
||||
distribution: ${{ matrix.distribution }}
|
||||
cache: maven
|
||||
- name: Display build environment details
|
||||
run: mvn --version
|
||||
|
||||
18
.github/workflows/deploy-website.yaml
vendored
18
.github/workflows/deploy-website.yaml
vendored
@@ -3,25 +3,22 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [ master, website ]
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
pages: write
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: ruby/setup-ruby@v1.117.0
|
||||
- uses: ruby/setup-ruby@v1.120.0
|
||||
with:
|
||||
working-directory: ./website
|
||||
bundler-cache: true
|
||||
- name: Configure Github Pages
|
||||
uses: actions/configure-pages@v2.1.1
|
||||
uses: actions/configure-pages@v2.1.2
|
||||
- name: Generate documentation
|
||||
run: ./generate-docs.sh
|
||||
- name: Build website with Jekyll
|
||||
@@ -39,6 +36,9 @@ jobs:
|
||||
deploy:
|
||||
if: github.ref == 'refs/heads/website'
|
||||
needs: build
|
||||
permissions:
|
||||
id-token: write
|
||||
pages: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment:
|
||||
name: github-pages
|
||||
@@ -46,4 +46,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1.2.1
|
||||
uses: actions/deploy-pages@v1.2.2
|
||||
|
||||
12
README.md
12
README.md
@@ -20,6 +20,7 @@ loves Error Prone: producing high-quality and consistent Java
|
||||
code_][picnic-blog-ep-post].
|
||||
|
||||
[![Maven Central][maven-central-badge]][maven-central-search]
|
||||
[![Reproducible Builds][reproducible-builds-badge]][reproducible-builds-report]
|
||||
[![GitHub Actions][github-actions-build-badge]][github-actions-build-master]
|
||||
[![License][license-badge]][license]
|
||||
[![PRs Welcome][pr-badge]][contributing]
|
||||
@@ -119,16 +120,13 @@ code with Maven should yield two compiler warnings:
|
||||
```sh
|
||||
$ mvn clean install
|
||||
...
|
||||
[INFO] -------------------------------------------------------------
|
||||
[WARNING] COMPILATION WARNING :
|
||||
[INFO] -------------------------------------------------------------
|
||||
[WARNING] Example.java:[9,34] [tech.picnic.errorprone.refasterrules.BigDecimalRules.BigDecimalZero]
|
||||
[INFO] Example.java:[9,34] [Refaster Rule] BigDecimalRules.BigDecimalZero: Refactoring opportunity
|
||||
(see https://error-prone.picnic.tech/refasterrules/BigDecimalRules#BigDecimalZero)
|
||||
Did you mean 'return BigDecimal.ZERO;'?
|
||||
...
|
||||
[WARNING] Example.java:[13,35] [IdentityConversion] This method invocation appears redundant; remove it or suppress this warning and add a comment explaining its purpose
|
||||
(see https://error-prone.picnic.tech/bugpatterns/IdentityConversion)
|
||||
Did you mean 'return set;' or '@SuppressWarnings("IdentityConversion") public ImmutableSet<Integer> getSet() {'?
|
||||
[INFO] 2 warnings
|
||||
[INFO] -------------------------------------------------------------
|
||||
...
|
||||
```
|
||||
|
||||
@@ -229,3 +227,5 @@ guidelines][contributing].
|
||||
[refaster]: https://errorprone.info/docs/refaster
|
||||
[refaster-rules-bigdecimal]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/BigDecimalRules.java
|
||||
[refaster-rules]: https://github.com/PicnicSupermarket/error-prone-support/blob/master/error-prone-contrib/src/main/java/tech/picnic/errorprone/refasterrules/
|
||||
[reproducible-builds-badge]: https://img.shields.io/badge/Reproducible_Builds-ok-success?labelColor=1e5b96
|
||||
[reproducible-builds-report]: https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/tech/picnic/error-prone-support/error-prone-support/README.md
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>error-prone-contrib</artifactId>
|
||||
@@ -64,11 +64,6 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
@@ -98,6 +93,11 @@
|
||||
<artifactId>reactor-adapter</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor.addons</groupId>
|
||||
<artifactId>reactor-extra</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
@@ -173,6 +173,11 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
|
||||
@@ -36,6 +36,9 @@ public final class AmbiguousJsonCreator extends BugChecker implements Annotation
|
||||
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
|
||||
isType("com.fasterxml.jackson.annotation.JsonCreator");
|
||||
|
||||
/** Instantiates a new {@link AmbiguousJsonCreator} instance. */
|
||||
public AmbiguousJsonCreator() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
if (!IS_JSON_CREATOR_ANNOTATION.matches(tree, state)) {
|
||||
|
||||
@@ -44,6 +44,9 @@ public final class AssertJIsNull extends BugChecker implements MethodInvocationT
|
||||
argumentCount(1),
|
||||
argument(0, nullLiteral()));
|
||||
|
||||
/** Instantiates a new {@link AssertJIsNull} instance. */
|
||||
public AssertJIsNull() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
|
||||
|
||||
@@ -38,6 +38,9 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
|
||||
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
|
||||
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
|
||||
|
||||
/** Instantiates a new {@link AutowiredConstructor} instance. */
|
||||
public AutowiredConstructor() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
List<MethodTree> constructors = ASTHelpers.getConstructors(tree);
|
||||
|
||||
@@ -46,6 +46,9 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntax::dropRedundantCurlies);
|
||||
|
||||
/** Instantiates a new {@link CanonicalAnnotationSyntax} instance. */
|
||||
public CanonicalAnnotationSyntax() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return FIX_FACTORIES.stream()
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.google.errorprone.matchers.Matcher;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import java.util.stream.Collector;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags {@link Collector Collectors} that don't clearly express
|
||||
@@ -45,9 +46,13 @@ public final class CollectorMutability extends BugChecker implements MethodInvoc
|
||||
private static final Matcher<ExpressionTree> SET_COLLECTOR =
|
||||
staticMethod().anyClass().named("toSet");
|
||||
|
||||
/** Instantiates a new {@link CollectorMutability} instance. */
|
||||
public CollectorMutability() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!COLLECTOR_METHOD.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)
|
||||
|| !COLLECTOR_METHOD.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
|
||||
AT_LEAST_ONE,
|
||||
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
|
||||
|
||||
/** Instantiates a new {@link EmptyMethod} instance. */
|
||||
public EmptyMethod() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (tree.getBody() == null
|
||||
|
||||
@@ -72,6 +72,9 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
|
||||
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
|
||||
.named("addOutputLines");
|
||||
|
||||
/** Instantiates a new {@link ErrorProneTestHelperSourceFormat} instance. */
|
||||
public ErrorProneTestHelperSourceFormat() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);
|
||||
|
||||
@@ -46,6 +46,9 @@ public final class ExplicitEnumOrdering extends BugChecker implements MethodInvo
|
||||
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
|
||||
staticMethod().onClass(Ordering.class.getName()).named("explicit");
|
||||
|
||||
/** Instantiates a new {@link ExplicitEnumOrdering} instance. */
|
||||
public ExplicitEnumOrdering() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!EXPLICIT_ORDERING.matches(tree, state)) {
|
||||
|
||||
@@ -5,6 +5,10 @@ import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -17,11 +21,14 @@ import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MemberReferenceTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@@ -33,11 +40,12 @@ import reactor.core.publisher.Flux;
|
||||
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
|
||||
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
|
||||
* avoids potentially saturating the thread pool on which subscription happens. If {@code
|
||||
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
|
||||
* {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
* concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code
|
||||
* flatMap} or {@code flatMapSequential} overload with an explicit concurrency level.
|
||||
*
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
|
||||
* by this check because there is no clear alternative to point to.
|
||||
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function,
|
||||
* java.util.function.Supplier)} is not flagged by this check because there is no clear alternative
|
||||
* to point to.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
@@ -52,11 +60,19 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
|
||||
private static final Supplier<Type> FLUX =
|
||||
Suppliers.typeFromString("reactor.core.publisher.Flux");
|
||||
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
|
||||
instanceMethod()
|
||||
.onDescendantOf("reactor.core.publisher.Flux")
|
||||
.onDescendantOf(FLUX)
|
||||
.namedAnyOf("flatMap", "flatMapSequential")
|
||||
.withParameters(Function.class.getName());
|
||||
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
|
||||
VisitorState.memoize(
|
||||
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
|
||||
/** Instantiates a new {@link FluxFlatMapUsage} instance. */
|
||||
public FluxFlatMapUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
@@ -64,14 +80,27 @@ public final class FluxFlatMapUsage extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree)
|
||||
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
|
||||
.addFix(
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build())
|
||||
.build();
|
||||
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
|
||||
SuggestedFix concurrencyCapFix =
|
||||
SuggestedFix.builder()
|
||||
.postfixWith(
|
||||
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
|
||||
.build();
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
|
||||
if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) {
|
||||
/*
|
||||
* Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g.
|
||||
* if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit
|
||||
* concurrently bound, in favour of sequential subscriptions using `Flux#concatMap`.
|
||||
*/
|
||||
description.addFix(concurrencyCapFix).addFix(serializationFix);
|
||||
} else {
|
||||
description.addFix(serializationFix).addFix(concurrencyCapFix);
|
||||
}
|
||||
|
||||
return description.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.sun.source.util.SimpleTreeVisitor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
@@ -129,6 +129,9 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
.namedAnyOf("debug", "error", "info", "trace", "warn");
|
||||
|
||||
/** Instantiates a new {@link FormatStringConcatenation} instance. */
|
||||
public FormatStringConcatenation() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (hasNonConstantStringConcatenationArgument(tree, 0, state)) {
|
||||
@@ -204,7 +207,7 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
}
|
||||
|
||||
private static class ReplacementArgumentsConstructor
|
||||
extends SimpleTreeVisitor<Void, VisitorState> {
|
||||
extends SimpleTreeVisitor<@Nullable Void, VisitorState> {
|
||||
private final StringBuilder formatString = new StringBuilder();
|
||||
private final List<Tree> formatArguments = new ArrayList<>();
|
||||
private final String formatSpecifier;
|
||||
@@ -213,9 +216,8 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
this.formatSpecifier = formatSpecifier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitBinary(BinaryTree tree, VisitorState state) {
|
||||
public @Nullable Void visitBinary(BinaryTree tree, VisitorState state) {
|
||||
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
|
||||
tree.getLeftOperand().accept(this, state);
|
||||
tree.getRightOperand().accept(this, state);
|
||||
@@ -226,15 +228,13 @@ public final class FormatStringConcatenation extends BugChecker
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
|
||||
public @Nullable Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
|
||||
return tree.getExpression().accept(this, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Void defaultAction(Tree tree, VisitorState state) {
|
||||
protected @Nullable Void defaultAction(Tree tree, VisitorState state) {
|
||||
appendExpression(tree);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
|
||||
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
|
||||
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
|
||||
|
||||
/** Instantiates a new {@link IdentityConversion} instance. */
|
||||
public IdentityConversion() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> arguments = tree.getArguments();
|
||||
|
||||
@@ -67,6 +67,9 @@ public final class ImmutablesSortedSetComparator extends BugChecker implements M
|
||||
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
|
||||
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
|
||||
|
||||
/** Instantiates a new {@link ImmutablesSortedSetComparator} instance. */
|
||||
public ImmutablesSortedSetComparator() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
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.LambdaExpressionTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.InstanceOfTree;
|
||||
import com.sun.source.tree.LambdaExpressionTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with a method reference
|
||||
* of the form {@code T.class::isInstance}.
|
||||
*
|
||||
* @see MethodReferenceUsage
|
||||
*/
|
||||
// XXX: Consider folding this logic into the `MethodReferenceUsage` check.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `Class::isInstance` method reference over equivalent lambda expression",
|
||||
link = BUG_PATTERNS_BASE_URL + "IsInstanceLambdaUsage",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link IsInstanceLambdaUsage} instance. */
|
||||
public IsInstanceLambdaUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
if (tree.getKind() != Kind.LAMBDA_EXPRESSION || tree.getBody().getKind() != Kind.INSTANCE_OF) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return describeMatch(
|
||||
tree,
|
||||
SuggestedFix.replace(
|
||||
tree,
|
||||
SourceCode.treeToString(((InstanceOfTree) tree.getBody()).getType(), state)
|
||||
+ ".class::isInstance"));
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,9 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
|
||||
isType("org.junit.jupiter.api.BeforeAll"),
|
||||
isType("org.junit.jupiter.api.BeforeEach")));
|
||||
|
||||
/** Instantiates a new {@link JUnitMethodDeclaration} instance. */
|
||||
public JUnitMethodDeclaration() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (HAS_UNMODIFIABLE_SIGNATURE.matches(tree, state)) {
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
|
||||
private final AnnotationAttributeMatcher matcher;
|
||||
|
||||
/** Instantiates the default {@link LexicographicalAnnotationAttributeListing}. */
|
||||
/** Instantiates a default {@link LexicographicalAnnotationAttributeListing} instance. */
|
||||
public LexicographicalAnnotationAttributeListing() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
@@ -184,17 +184,15 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
private static ImmutableList<ImmutableList<String>> getStructure(ExpressionTree array) {
|
||||
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
|
||||
|
||||
new TreeScanner<Void, Void>() {
|
||||
@Nullable
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
public @Nullable Void visitIdentifier(IdentifierTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getName().toString()));
|
||||
return super.visitIdentifier(node, unused);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitLiteral(LiteralTree node, @Nullable Void unused) {
|
||||
public @Nullable Void visitLiteral(LiteralTree node, @Nullable Void unused) {
|
||||
Object value = ASTHelpers.constValue(node);
|
||||
nodes.add(
|
||||
value instanceof String
|
||||
@@ -204,9 +202,8 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
|
||||
return super.visitLiteral(node, unused);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
|
||||
public @Nullable Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void unused) {
|
||||
nodes.add(ImmutableList.of(node.getPrimitiveTypeKind().toString()));
|
||||
return super.visitPrimitiveType(node, unused);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
|
||||
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.DECLARATION;
|
||||
import static com.sun.tools.javac.code.TypeAnnotations.AnnotationType.TYPE;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
@@ -17,10 +20,14 @@ import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.TypeAnnotations.AnnotationType;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
@@ -39,6 +46,12 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
public final class LexicographicalAnnotationListing extends BugChecker
|
||||
implements MethodTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Comparator<@Nullable AnnotationType> BY_ANNOTATION_TYPE =
|
||||
(a, b) ->
|
||||
(a == null || a == DECLARATION) && b == TYPE ? -1 : a == TYPE && b == DECLARATION ? 1 : 0;
|
||||
|
||||
/** Instantiates a new {@link LexicographicalAnnotationListing} instance. */
|
||||
public LexicographicalAnnotationListing() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
@@ -47,26 +60,29 @@ public final class LexicographicalAnnotationListing extends BugChecker
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations = sort(originalOrdering, state);
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations =
|
||||
sort(originalOrdering, ASTHelpers.getSymbol(tree), state);
|
||||
if (originalOrdering.equals(sortedAnnotations)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
|
||||
|
||||
Description.Builder description = buildDescription(originalOrdering.get(0));
|
||||
fix.ifPresent(description::addFix);
|
||||
return description.build();
|
||||
return describeMatch(
|
||||
originalOrdering.get(0), fixOrdering(originalOrdering, sortedAnnotations, state));
|
||||
}
|
||||
|
||||
private static ImmutableList<? extends AnnotationTree> sort(
|
||||
List<? extends AnnotationTree> annotations, VisitorState state) {
|
||||
List<? extends AnnotationTree> annotations, Symbol symbol, VisitorState state) {
|
||||
return annotations.stream()
|
||||
.sorted(comparing(annotation -> SourceCode.treeToString(annotation, state)))
|
||||
.sorted(
|
||||
comparing(
|
||||
(AnnotationTree annotation) ->
|
||||
ASTHelpers.getAnnotationType(annotation, symbol, state),
|
||||
BY_ANNOTATION_TYPE)
|
||||
.thenComparing(annotation -> SourceCode.treeToString(annotation, state)))
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
private static Optional<Fix> tryFixOrdering(
|
||||
private static Fix fixOrdering(
|
||||
List<? extends AnnotationTree> originalAnnotations,
|
||||
ImmutableList<? extends AnnotationTree> sortedAnnotations,
|
||||
VisitorState state) {
|
||||
@@ -77,6 +93,7 @@ public final class LexicographicalAnnotationListing extends BugChecker
|
||||
SuggestedFix.builder()
|
||||
.replace(original, SourceCode.treeToString(replacement, state)))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build);
|
||||
.map(SuggestedFix.Builder::build)
|
||||
.orElseThrow(() -> new VerifyException("No annotations were provided"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags lambda expressions that can be replaced with method references.
|
||||
*
|
||||
* @see IsInstanceLambdaUsage
|
||||
*/
|
||||
// XXX: Other custom expressions we could rewrite:
|
||||
// - `a -> "str" + a` to `"str"::concat`. But only if `str` is provably non-null.
|
||||
@@ -50,6 +52,9 @@ import javax.lang.model.element.Name;
|
||||
// `not(some::reference)`.
|
||||
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
|
||||
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
|
||||
// XXX: Expressions of the form `i -> SomeType.class.isInstance(i)` are not replaced; fix that using
|
||||
// a suitable generalization.
|
||||
// XXX: Consider folding the `IsInstanceLambdaUsage` check into this class.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer method references over lambda expressions",
|
||||
@@ -60,6 +65,9 @@ import javax.lang.model.element.Name;
|
||||
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link MethodReferenceUsage} instance. */
|
||||
public MethodReferenceUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
|
||||
/*
|
||||
@@ -122,7 +130,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
return Optional.empty();
|
||||
}
|
||||
Symbol sym = ASTHelpers.getSymbol(methodSelect);
|
||||
if (!sym.isStatic()) {
|
||||
if (!ASTHelpers.isStatic(sym)) {
|
||||
return constructFix(lambdaExpr, "this", methodSelect);
|
||||
}
|
||||
return constructFix(lambdaExpr, sym.owner, methodSelect);
|
||||
@@ -192,7 +200,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
|
||||
Name sName = target.getSimpleName();
|
||||
Optional<SuggestedFix.Builder> fix = constructFix(lambdaExpr, sName, methodName);
|
||||
|
||||
if (!"java.lang".equals(target.packge().toString())) {
|
||||
if (!"java.lang".equals(ASTHelpers.enclosingPackage(target).toString())) {
|
||||
Name fqName = target.getQualifiedName();
|
||||
if (!sName.equals(fqName)) {
|
||||
return fix.map(b -> b.addImport(fqName.toString()));
|
||||
|
||||
@@ -40,6 +40,9 @@ public final class MissingRefasterAnnotation extends BugChecker implements Class
|
||||
isType("com.google.errorprone.refaster.annotation.BeforeTemplate"),
|
||||
isType("com.google.errorprone.refaster.annotation.AfterTemplate")));
|
||||
|
||||
/** Instantiates a new {@link MissingRefasterAnnotation} instance. */
|
||||
public MissingRefasterAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
long methodTypes =
|
||||
|
||||
@@ -36,6 +36,9 @@ public final class MockitoStubbing extends BugChecker implements MethodInvocatio
|
||||
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
|
||||
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
|
||||
|
||||
/** Instantiates a new {@link MockitoStubbing} instance. */
|
||||
public MockitoStubbing() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> arguments = tree.getArguments();
|
||||
|
||||
@@ -4,9 +4,11 @@ import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
@@ -16,9 +18,7 @@ import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** A {@link BugChecker} that flags nesting of {@link Optional Optionals}. */
|
||||
@@ -33,21 +33,16 @@ import java.util.Optional;
|
||||
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
|
||||
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
|
||||
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
|
||||
|
||||
/** Instantiates a new {@link NestedOptionals} instance. */
|
||||
public NestedOptionals() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
|
||||
Type optionalType = OPTIONAL.get(state);
|
||||
Type type = ASTHelpers.getType(tree);
|
||||
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Type> typeArguments = type.getTypeArguments();
|
||||
return !typeArguments.isEmpty()
|
||||
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
|
||||
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
|
||||
? describeMatch(tree)
|
||||
: Description.NO_MATCH;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ public final class NonEmptyMono extends BugChecker implements MethodInvocationTr
|
||||
.onDescendantOf("reactor.core.publisher.Mono")
|
||||
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
|
||||
|
||||
/** Instantiates a new {@link NonEmptyMono} instance. */
|
||||
public NonEmptyMono() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!MONO_SIZE_CHECK.matches(tree, state)) {
|
||||
|
||||
@@ -70,6 +70,9 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
|
||||
.named("thenComparing")
|
||||
.withParameters(Function.class.getName()));
|
||||
|
||||
/** Instantiates a new {@link PrimitiveComparison} instance. */
|
||||
public PrimitiveComparison() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isStatic = STATIC_COMPARISON_METHOD.matches(tree, state);
|
||||
|
||||
@@ -141,7 +141,7 @@ public final class RedundantStringConversion extends BugChecker
|
||||
|
||||
private final Matcher<MethodInvocationTree> conversionMethodMatcher;
|
||||
|
||||
/** Instantiates the default {@link RedundantStringConversion}. */
|
||||
/** Instantiates a default {@link RedundantStringConversion} instance. */
|
||||
public RedundantStringConversion() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public final class RefasterAnyOfUsage extends BugChecker implements MethodInvoca
|
||||
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
|
||||
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
|
||||
|
||||
/** Instantiates a new {@link RefasterAnyOfUsage} instance. */
|
||||
public RefasterAnyOfUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (REFASTER_ANY_OF.matches(tree, state)) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.Set;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which suggests a canonical set of modifiers for Refaster class and method
|
||||
* A {@link BugChecker} that suggests a canonical set of modifiers for Refaster class and method
|
||||
* definitions.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -48,6 +48,9 @@ public final class RefasterRuleModifiers extends BugChecker
|
||||
private static final Matcher<Tree> REFASTER_METHOD =
|
||||
anyOf(BEFORE_TEMPLATE_METHOD, AFTER_TEMPLATE_METHOD, PLACEHOLDER_METHOD);
|
||||
|
||||
/** Instantiates a new {@link RefasterRuleModifiers} instance. */
|
||||
public RefasterRuleModifiers() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
if (!hasMatchingMember(tree, BEFORE_TEMPLATE_METHOD, state)) {
|
||||
|
||||
@@ -83,6 +83,9 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
|
||||
isSameType("org.springframework.web.util.UriBuilder"),
|
||||
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
|
||||
|
||||
/** Instantiates a new {@link RequestMappingAnnotation} instance. */
|
||||
public RequestMappingAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
// XXX: Auto-add `@RequestParam` where applicable.
|
||||
|
||||
@@ -37,6 +37,9 @@ public final class RequestParamType extends BugChecker implements VariableTreeMa
|
||||
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
|
||||
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)));
|
||||
|
||||
/** Instantiates a new {@link RequestParamType} instance. */
|
||||
public RequestParamType() {}
|
||||
|
||||
@Override
|
||||
public Description matchVariable(VariableTree tree, VisitorState state) {
|
||||
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags methods with Spring's {@code @Scheduled} annotation that lack New
|
||||
@@ -46,9 +47,13 @@ public final class ScheduledTransactionTrace extends BugChecker implements Metho
|
||||
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
|
||||
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
|
||||
|
||||
/** Instantiates a new {@link ScheduledTransactionTrace} instance. */
|
||||
public ScheduledTransactionTrace() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (!IS_SCHEDULED.matches(tree, state)) {
|
||||
if (!ThirdPartyLibrary.NEW_RELIC_AGENT_API.isIntroductionAllowed(state)
|
||||
|| !IS_SCHEDULED.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.fixes.Fix;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.tools.javac.code.Scope;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags annotations with time attributes that can be written more
|
||||
* concisely.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Simplifies annotations which express an amount of time using a `TimeUnit`",
|
||||
link = BUG_PATTERNS_BASE_URL + "SimplifyTimeAnnotation",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class SimplifyTimeAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
|
||||
createAnnotationAttributeMatcher();
|
||||
|
||||
/** Instantiates a new {@link SimplifyTimeAnnotationCheck} instance. */
|
||||
public SimplifyTimeAnnotationCheck() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree annotationTree, VisitorState state) {
|
||||
ImmutableList<ExpressionTree> arguments =
|
||||
ARGUMENT_SELECTOR.extractMatchingArguments(annotationTree).collect(toImmutableList());
|
||||
|
||||
if (arguments.isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return trySimplification(annotationTree, arguments, state)
|
||||
.map(fix -> describeMatch(annotationTree, fix))
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> trySimplification(
|
||||
AnnotationTree annotation, ImmutableList<ExpressionTree> arguments, VisitorState state) {
|
||||
checkArgument(!arguments.isEmpty());
|
||||
|
||||
AnnotationDescriptor annotationDescriptor =
|
||||
AnnotationDescriptor.from(getAnnotationFqcn(annotation));
|
||||
if (containsAnyAttributeOf(annotation, annotationDescriptor.bannedFields)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ImmutableMap<String, ExpressionTree> indexedAttributes =
|
||||
Maps.uniqueIndex(
|
||||
arguments,
|
||||
expr ->
|
||||
ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
|
||||
.getSimpleName()
|
||||
.toString());
|
||||
|
||||
TimeUnit currentTimeUnit =
|
||||
getTimeUnit(annotation, annotationDescriptor.timeUnitField, indexedAttributes);
|
||||
|
||||
ImmutableMap<String, Number> timeValues =
|
||||
annotationDescriptor.timeFields.stream()
|
||||
.map(field -> Map.entry(field, getValue(field, indexedAttributes)))
|
||||
.filter(entry -> entry.getValue().isPresent())
|
||||
.collect(toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().orElseThrow()));
|
||||
|
||||
Map<String, TimeSimplifier.Simplification> simplifications =
|
||||
Maps.transformValues(
|
||||
Maps.filterValues(
|
||||
Maps.transformEntries(
|
||||
timeValues, (field, value) -> trySimplify(value, currentTimeUnit)),
|
||||
Optional::isPresent),
|
||||
Optional::orElseThrow);
|
||||
|
||||
// Some could not be simplified, and since the unit is shared, the others can't either.
|
||||
if (simplifications.size() != timeValues.size()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// The annotation is of the form `@Annotation(v)` or `@Annotation(value = v)`. For the former we
|
||||
// must synthesize the entire annotation, but this is OK for the latter, too.
|
||||
if (indexedAttributes.size() == 1 && simplifications.containsKey("value")) {
|
||||
TimeSimplifier.Simplification simplification = simplifications.get("value");
|
||||
return Optional.of(
|
||||
getImplicitValueAttributeFix(
|
||||
annotation,
|
||||
simplification.value,
|
||||
annotationDescriptor.timeUnitField,
|
||||
simplification.timeUnit,
|
||||
state));
|
||||
}
|
||||
|
||||
// Since each might have a different simplification possible, check the common unit.
|
||||
// Since we only get simplifications iff it's possible, and we check that all can be simplified,
|
||||
// we don't need to check if this equals `currentTimeUnit`.
|
||||
TimeUnit commonUnit =
|
||||
findCommonUnit(
|
||||
ImmutableSet.copyOf(
|
||||
Maps.transformValues(simplifications, simplification -> simplification.timeUnit)
|
||||
.values()));
|
||||
|
||||
return getExplicitAttributesFix(
|
||||
annotation, simplifications, annotationDescriptor.timeUnitField, commonUnit, state);
|
||||
}
|
||||
|
||||
private static boolean containsAnyAttributeOf(
|
||||
AnnotationTree annotation, ImmutableSet<String> attributes) {
|
||||
return annotation.getArguments().stream()
|
||||
.map(
|
||||
expr ->
|
||||
expr.getKind() == Tree.Kind.ASSIGNMENT
|
||||
? ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable())
|
||||
.getSimpleName()
|
||||
.toString()
|
||||
: "value")
|
||||
.anyMatch(attributes::contains);
|
||||
}
|
||||
|
||||
private static Fix getImplicitValueAttributeFix(
|
||||
AnnotationTree annotation,
|
||||
long newValue,
|
||||
String timeUnitField,
|
||||
TimeUnit newTimeUnit,
|
||||
VisitorState state) {
|
||||
String synthesizedAnnotation =
|
||||
SourceCode.treeToString(annotation, state)
|
||||
.replaceFirst(
|
||||
"\\(.+\\)",
|
||||
String.format("(value=%s, %s=%s)", newValue, timeUnitField, newTimeUnit.name()));
|
||||
return SuggestedFix.builder()
|
||||
.replace(annotation, synthesizedAnnotation)
|
||||
.addStaticImport(TimeUnit.class.getName() + '.' + newTimeUnit.name())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Optional<Fix> getExplicitAttributesFix(
|
||||
AnnotationTree annotation,
|
||||
Map<String, TimeSimplifier.Simplification> simplifications,
|
||||
String timeUnitField,
|
||||
TimeUnit newUnit,
|
||||
VisitorState state) {
|
||||
return simplifications.entrySet().stream()
|
||||
.map(
|
||||
simplificationEntry ->
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
annotation, state, timeUnitField, ImmutableList.of(newUnit.name()))
|
||||
.merge(
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
annotation,
|
||||
state,
|
||||
simplificationEntry.getKey(),
|
||||
ImmutableList.of(
|
||||
String.valueOf(simplificationEntry.getValue().toUnit(newUnit))))))
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(builder -> builder.addStaticImport(TimeUnit.class.getName() + '.' + newUnit.name()))
|
||||
.map(SuggestedFix.Builder::build);
|
||||
}
|
||||
|
||||
private static String getAnnotationFqcn(AnnotationTree annotation) {
|
||||
return ASTHelpers.getSymbol(annotation).getQualifiedName().toString();
|
||||
}
|
||||
|
||||
private static Optional<Number> getValue(
|
||||
String field, ImmutableMap<String, ExpressionTree> indexedArguments) {
|
||||
return Optional.ofNullable(indexedArguments.get(field))
|
||||
.filter(AssignmentTree.class::isInstance)
|
||||
.map(AssignmentTree.class::cast)
|
||||
.map(AssignmentTree::getExpression)
|
||||
.map(expr -> ASTHelpers.constValue(expr, Number.class));
|
||||
}
|
||||
|
||||
private static TimeUnit getTimeUnit(
|
||||
AnnotationTree annotation,
|
||||
String field,
|
||||
ImmutableMap<String, ExpressionTree> indexedArguments) {
|
||||
VarSymbol symbol =
|
||||
Optional.ofNullable(indexedArguments.get(field))
|
||||
.map(
|
||||
argumentTree ->
|
||||
(VarSymbol)
|
||||
ASTHelpers.getSymbol(((AssignmentTree) argumentTree).getExpression()))
|
||||
.orElseGet(() -> getDefaultTimeUnit(annotation, field));
|
||||
return TimeUnit.valueOf(symbol.getQualifiedName().toString());
|
||||
}
|
||||
|
||||
private static VarSymbol getDefaultTimeUnit(AnnotationTree annotation, String argument) {
|
||||
Scope scope = ASTHelpers.getSymbol(annotation).members();
|
||||
MethodSymbol argumentSymbol =
|
||||
(MethodSymbol)
|
||||
Iterables.getOnlyElement(
|
||||
ASTHelpers.scope(scope)
|
||||
.getSymbols(symbol -> symbol.getQualifiedName().contentEquals(argument)));
|
||||
return (VarSymbol)
|
||||
requireNonNull(argumentSymbol.getDefaultValue(), "Default value missing").getValue();
|
||||
}
|
||||
|
||||
private static AnnotationAttributeMatcher createAnnotationAttributeMatcher() {
|
||||
ImmutableList<String> toMatch =
|
||||
Arrays.stream(AnnotationDescriptor.values())
|
||||
.flatMap(
|
||||
annotation ->
|
||||
annotation.timeFields.stream().map(field -> annotation.fqcn + '#' + field))
|
||||
.collect(toImmutableList());
|
||||
return AnnotationAttributeMatcher.create(Optional.of(toMatch), ImmutableList.of());
|
||||
}
|
||||
|
||||
private static Optional<TimeSimplifier.Simplification> trySimplify(Number value, TimeUnit unit) {
|
||||
checkArgument(
|
||||
value instanceof Integer || value instanceof Long,
|
||||
"Only time expressed as an integer or long can be simplified");
|
||||
return TimeSimplifier.simplify(value.longValue(), unit);
|
||||
}
|
||||
|
||||
private static TimeUnit findCommonUnit(ImmutableSet<TimeUnit> units) {
|
||||
return ImmutableSortedSet.copyOf(units).first();
|
||||
}
|
||||
|
||||
private enum AnnotationDescriptor {
|
||||
JUNIT_TIMEOUT("org.junit.jupiter.api.Timeout", ImmutableSet.of("value"), "unit"),
|
||||
SPRING_SCHEDULED(
|
||||
"org.springframework.scheduling.annotation.Scheduled",
|
||||
ImmutableSet.of("fixedDelay", "fixedRate", "initialDelay"),
|
||||
"timeUnit",
|
||||
ImmutableSet.of("fixedDelayString", "fixedRateString", "initialDelayString"));
|
||||
|
||||
/** The fully-qualified class name of the annotation to simplify. */
|
||||
private final String fqcn;
|
||||
/** The attributes containing a value of time. */
|
||||
private final ImmutableSet<String> timeFields;
|
||||
/** The attribute containing the time unit. */
|
||||
private final String timeUnitField;
|
||||
/** The set of attributes that cause the check to back off. */
|
||||
private final ImmutableSet<String> bannedFields;
|
||||
|
||||
AnnotationDescriptor(String fqcn, ImmutableSet<String> timeFields, String timeUnitField) {
|
||||
this(fqcn, timeFields, timeUnitField, ImmutableSet.of());
|
||||
}
|
||||
|
||||
AnnotationDescriptor(
|
||||
String fqcn,
|
||||
ImmutableSet<String> timeFields,
|
||||
String timeUnitField,
|
||||
ImmutableSet<String> bannedFields) {
|
||||
this.fqcn = fqcn;
|
||||
this.timeFields = timeFields;
|
||||
this.timeUnitField = timeUnitField;
|
||||
this.bannedFields = bannedFields;
|
||||
}
|
||||
|
||||
public static AnnotationDescriptor from(String fqcn) {
|
||||
return Arrays.stream(values())
|
||||
.filter(annotation -> annotation.fqcn.equals(fqcn))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"Unknown enum constant: %s.%s",
|
||||
AnnotationDescriptor.class.getName(), fqcn)));
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility class to help simplify time expressions. */
|
||||
private static final class TimeSimplifier {
|
||||
private static final ImmutableSortedSet<TimeUnit> TIME_UNITS =
|
||||
ImmutableSortedSet.copyOf(TimeUnit.values());
|
||||
|
||||
/**
|
||||
* Returns a {@link Simplification} (iff possible) that describes how the {@code originalValue}
|
||||
* and {@code originalUnit} can be simplified using a larger {@link TimeUnit}.
|
||||
*/
|
||||
static Optional<Simplification> simplify(long originalValue, TimeUnit originalUnit) {
|
||||
return descendingLargerUnits(originalUnit).stream()
|
||||
.flatMap(unit -> trySimplify(originalValue, originalUnit, unit))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Stream<Simplification> trySimplify(
|
||||
long originalValue, TimeUnit originalUnit, TimeUnit unit) {
|
||||
long converted = unit.convert(originalValue, originalUnit);
|
||||
// Check whether we lose any precision by checking whether we can convert back.
|
||||
return originalValue == originalUnit.convert(converted, unit)
|
||||
? Stream.of(new Simplification(converted, unit))
|
||||
: Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all time units that represent a larger amount of time than {@code unit}, in
|
||||
* descending order.
|
||||
*/
|
||||
private static ImmutableSortedSet<TimeUnit> descendingLargerUnits(TimeUnit unit) {
|
||||
return TIME_UNITS.tailSet(unit, /* inclusive= */ false).descendingSet();
|
||||
}
|
||||
|
||||
/** Represents a simplification in terms of the new value and new unit. */
|
||||
private static final class Simplification {
|
||||
private final long value;
|
||||
private final TimeUnit timeUnit;
|
||||
|
||||
Simplification(long value, TimeUnit timeUnit) {
|
||||
this.value = value;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the value with the unit represented by this simplification to an equivalent value
|
||||
* in the given {@code unit}.
|
||||
*/
|
||||
public long toUnit(TimeUnit unit) {
|
||||
return unit.convert(value, timeUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ public final class Slf4jLogStatement extends BugChecker implements MethodInvocat
|
||||
.onDescendantOf("org.slf4j.Logger")
|
||||
.namedAnyOf("trace", "debug", "info", "warn", "error");
|
||||
|
||||
/** Instantiates a new {@link Slf4jLogStatement} instance. */
|
||||
public Slf4jLogStatement() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!SLF4J_LOGGER_INVOCATION.matches(tree, state)) {
|
||||
|
||||
@@ -57,6 +57,9 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
|
||||
.put("PUT", "PutMapping")
|
||||
.build();
|
||||
|
||||
/** Instantiates a new {@link SpringMvcAnnotation} instance. */
|
||||
public SpringMvcAnnotation() {}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
// XXX: We could remove the `@RequestMapping` import if not other usages remain.
|
||||
|
||||
@@ -101,7 +101,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"org.springframework.http.HttpMethod",
|
||||
"org.springframework.http.MediaType",
|
||||
"org.testng.Assert",
|
||||
"reactor.function.TupleUtils");
|
||||
"reactor.function.TupleUtils",
|
||||
"tech.picnic.errorprone.bugpatterns.util.MoreTypes");
|
||||
|
||||
/** Type members that should be statically imported. */
|
||||
@VisibleForTesting
|
||||
@@ -190,6 +191,9 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
|
||||
"of",
|
||||
"valueOf");
|
||||
|
||||
/** Instantiates a new {@link StaticImport} instance. */
|
||||
public StaticImport() {}
|
||||
|
||||
@Override
|
||||
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
|
||||
if (!isCandidateContext(state) || !isCandidate(tree)) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
||||
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -26,12 +27,12 @@ import com.sun.tools.javac.util.Convert;
|
||||
import java.util.Formattable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} which flags {@link String#format(String, Object...)} invocations which can
|
||||
* be replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
|
||||
* A {@link BugChecker} that flags {@link String#format(String, Object...)} invocations which can be
|
||||
* replaced with a {@link String#join(CharSequence, CharSequence...)} or even a {@link
|
||||
* String#valueOf} invocation.
|
||||
*/
|
||||
// XXX: What about `v1 + "sep" + v2` and similar expressions? Do we want to rewrite those to
|
||||
@@ -40,7 +41,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Prefer `String#join` over `String#format`",
|
||||
linkType = NONE,
|
||||
link = BUG_PATTERNS_BASE_URL + "StringJoin",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION,
|
||||
tags = SIMPLIFICATION)
|
||||
public final class StringJoin extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
@@ -52,6 +54,9 @@ public final class StringJoin extends BugChecker implements MethodInvocationTree
|
||||
Suppliers.typeFromClass(CharSequence.class);
|
||||
private static final Supplier<Type> FORMATTABLE_TYPE = Suppliers.typeFromClass(Formattable.class);
|
||||
|
||||
/** Instantiates a new {@link StringJoin} instance. */
|
||||
public StringJoin() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (!STRING_FORMAT_INVOCATION.matches(tree, state)) {
|
||||
|
||||
@@ -26,6 +26,9 @@ import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/** A {@link BugChecker} that flags illegal time-zone related operations. */
|
||||
@AutoService(BugChecker.class)
|
||||
@@ -58,10 +61,16 @@ public final class TimeZoneUsage extends BugChecker implements MethodInvocationT
|
||||
.onClassAny(
|
||||
LocalDate.class.getName(),
|
||||
LocalDateTime.class.getName(),
|
||||
LocalTime.class.getName())
|
||||
LocalTime.class.getName(),
|
||||
OffsetDateTime.class.getName(),
|
||||
OffsetTime.class.getName(),
|
||||
ZonedDateTime.class.getName())
|
||||
.named("now"),
|
||||
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
|
||||
|
||||
/** Instantiates a new {@link TimeZoneUsage} instance. */
|
||||
public TimeZoneUsage() {}
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
return BANNED_TIME_METHOD.matches(tree, state)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Picnic Error Prone Contrib checks. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
@@ -21,6 +21,9 @@ public final class MethodMatcherFactory {
|
||||
private static final Pattern METHOD_SIGNATURE =
|
||||
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
|
||||
|
||||
/** Instantiates a new {@link MethodMatcherFactory} instance. */
|
||||
public MethodMatcherFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a {@link Matcher} of methods with any of the given signatures.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.suppliers.Suppliers;
|
||||
import com.sun.tools.javac.code.BoundKind;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* A set of helper methods which together define a DSL for defining {@link Type types}.
|
||||
*
|
||||
* <p>These methods are meant to be statically imported. Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* Supplier<Type> type =
|
||||
* VisitorState.memoize(
|
||||
* generic(
|
||||
* type("reactor.core.publisher.Flux"),
|
||||
* subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
|
||||
* }</pre>
|
||||
*
|
||||
* This statement produces a memoized supplier of the type {@code Flux<? extends Publisher<?>>}.
|
||||
*/
|
||||
public final class MoreTypes {
|
||||
private MoreTypes() {}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the type with the given fully qualified name.
|
||||
*
|
||||
* <p>This method should only be used when building more complex types in combination with other
|
||||
* {@link MoreTypes} methods. In other cases prefer directly calling {@link
|
||||
* Suppliers#typeFromString(String)}.
|
||||
*
|
||||
* @param typeName The type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> type(String typeName) {
|
||||
return Suppliers.typeFromString(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a supplier of the described generic type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @param typeArgs The desired type arguments.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
// XXX: The given `type` should be a generic type, so perhaps `withParams` would be a better
|
||||
// method name. But the DSL wouldn't look as nice that way.
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) -> {
|
||||
List<Type> params =
|
||||
Arrays.stream(typeArgs).map(s -> s.get(state)).collect(toCollection(ArrayList::new));
|
||||
if (params.stream().anyMatch(Objects::isNull)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return state.getType(baseType, /* isArray= */ false, params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a raw (erased, non-generic) variant of the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> raw(Supplier<Type> type) {
|
||||
return propagateNull(type, (state, baseType) -> baseType.tsym.erasure(state.getTypes()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? super T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> superOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(baseType, BoundKind.SUPER, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ? extends T} wildcard type, with {@code T} bound to the given type.
|
||||
*
|
||||
* @param type The base type of interest.
|
||||
* @return A supplier which returns the described type if available in the given state, and {@code
|
||||
* null} otherwise.
|
||||
*/
|
||||
public static Supplier<Type> subOf(Supplier<Type> type) {
|
||||
return propagateNull(
|
||||
type,
|
||||
(state, baseType) ->
|
||||
new Type.WildcardType(
|
||||
type.get(state), BoundKind.EXTENDS, state.getSymtab().boundClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unbound wildcard type ({@code ?}).
|
||||
*
|
||||
* @return A supplier which returns the described type.
|
||||
*/
|
||||
public static Supplier<Type> unbound() {
|
||||
return state ->
|
||||
new Type.WildcardType(
|
||||
state.getSymtab().objectType, BoundKind.UNBOUND, state.getSymtab().boundClass);
|
||||
}
|
||||
|
||||
private static Supplier<Type> propagateNull(
|
||||
Supplier<Type> type, BiFunction<VisitorState, Type, Type> transformer) {
|
||||
return state ->
|
||||
Optional.ofNullable(type.get(state)).map(t -> transformer.apply(state, t)).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.sun.tools.javac.code.ClassFinder;
|
||||
import com.sun.tools.javac.code.Symbol.CompletionFailure;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
|
||||
/**
|
||||
* Utility class that helps decide whether it is appropriate to introduce references to (well-known)
|
||||
* third-party libraries.
|
||||
*
|
||||
* <p>This class should be used by {@link BugChecker}s that may otherwise suggest the introduction
|
||||
* of code that depends on possibly-not-present third-party libraries.
|
||||
*/
|
||||
// XXX: Consider giving users more fine-grained control. This would be beneficial in cases where a
|
||||
// dependency is on the classpath, but new usages are undesirable.
|
||||
public enum ThirdPartyLibrary {
|
||||
/**
|
||||
* AssertJ.
|
||||
*
|
||||
* @see <a href="https://assertj.github.io/doc">AssertJ documentation</a>
|
||||
*/
|
||||
ASSERTJ("org.assertj.core.api.Assertions"),
|
||||
/**
|
||||
* Google's Guava.
|
||||
*
|
||||
* @see <a href="https://github.com/google/guava">Guava on GitHub</a>
|
||||
*/
|
||||
GUAVA("com.google.common.collect.ImmutableList"),
|
||||
/**
|
||||
* New Relic's Java agent API.
|
||||
*
|
||||
* @see <a href="https://github.com/newrelic/newrelic-java-agent/tree/main/newrelic-api">New Relic
|
||||
* Java agent API on GitHub</a>
|
||||
*/
|
||||
NEW_RELIC_AGENT_API("com.newrelic.api.agent.Agent"),
|
||||
/**
|
||||
* VMWare's Project Reactor.
|
||||
*
|
||||
* @see <a href="https://projectreactor.io">Home page</a>
|
||||
*/
|
||||
REACTOR("reactor.core.publisher.Flux");
|
||||
|
||||
private static final String IGNORE_CLASSPATH_COMPAT_FLAG =
|
||||
"ErrorProneSupport:IgnoreClasspathCompat";
|
||||
|
||||
@SuppressWarnings("ImmutableEnumChecker" /* Supplier is deterministic. */)
|
||||
private final Supplier<Boolean> canUse;
|
||||
|
||||
/**
|
||||
* Instantiates a {@link ThirdPartyLibrary} enum value.
|
||||
*
|
||||
* @param witnessFqcn The fully-qualified class name of a type that is expected to be on the
|
||||
* classpath iff the associated third-party library is on the classpath.
|
||||
*/
|
||||
ThirdPartyLibrary(String witnessFqcn) {
|
||||
this.canUse = VisitorState.memoize(state -> canIntroduceUsage(witnessFqcn, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether it is okay to introduce a dependency on this well-known third party library in
|
||||
* the given context.
|
||||
*
|
||||
* @param state The context under consideration.
|
||||
* @return {@code true} iff it is okay to assume or create a dependency on this library.
|
||||
*/
|
||||
public boolean isIntroductionAllowed(VisitorState state) {
|
||||
return canUse.get(state);
|
||||
}
|
||||
|
||||
private static boolean canIntroduceUsage(String className, VisitorState state) {
|
||||
return shouldIgnoreClasspath(state) || isKnownClass(className, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to determine whether a class with the given FQCN is on the classpath.
|
||||
*
|
||||
* <p>The {@link VisitorState}'s symbol table is consulted first. If the type has not yet been
|
||||
* loaded, then an attempt is made to do so.
|
||||
*/
|
||||
private static boolean isKnownClass(String className, VisitorState state) {
|
||||
return state.getTypeFromString(className) != null || canLoadClass(className, state);
|
||||
}
|
||||
|
||||
private static boolean canLoadClass(String className, VisitorState state) {
|
||||
ClassFinder classFinder = ClassFinder.instance(state.context);
|
||||
Name binaryName = state.binaryNameFromClassname(className);
|
||||
try {
|
||||
classFinder.loadClass(state.getSymtab().unnamedModule, binaryName);
|
||||
return true;
|
||||
} catch (CompletionFailure e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldIgnoreClasspath(VisitorState state) {
|
||||
return state
|
||||
.errorProneOptions()
|
||||
.getFlags()
|
||||
.getBoolean(IGNORE_CLASSPATH_COMPAT_FLAG)
|
||||
.orElse(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Auxiliary utilities for use by Error Prone checks. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
@@ -27,7 +27,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/**
|
||||
@@ -46,11 +46,33 @@ final class AssortedRules {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
int after(int index, int size) {
|
||||
return checkIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Objects#checkIndex(int, int)} over less descriptive or more verbose alternatives.
|
||||
*
|
||||
* <p>If a custom error message is desired, consider using Guava's {@link
|
||||
* com.google.common.base.Preconditions#checkElementIndex(int, int, String)}.
|
||||
*/
|
||||
static final class CheckIndexConditional {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size) {
|
||||
checkIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: We could add a rule for `new EnumMap(Map<K, ? extends V> m)`, but that constructor does
|
||||
// not allow an empty non-EnumMap to be provided.
|
||||
static final class CreateEnumMap<K extends Enum<K>, V> {
|
||||
@@ -67,14 +89,12 @@ final class AssortedRules {
|
||||
|
||||
static final class MapGetOrNull<K, V, L> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
V before(Map<K, V> map, L key) {
|
||||
@Nullable V before(Map<K, V> map, L key) {
|
||||
return map.getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
V after(Map<K, V> map, L key) {
|
||||
@Nullable V after(Map<K, V> map, L key) {
|
||||
return map.get(key);
|
||||
}
|
||||
}
|
||||
@@ -112,8 +132,7 @@ final class AssortedRules {
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@Nullable
|
||||
T after(Iterator<T> iterator, T defaultValue) {
|
||||
@Nullable T after(Iterator<T> iterator, T defaultValue) {
|
||||
return Iterators.getNext(iterator, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link Multimap}s. */
|
||||
@@ -50,8 +50,7 @@ final class MultimapRules {
|
||||
*/
|
||||
static final class MultimapGet<K, V> {
|
||||
@BeforeTemplate
|
||||
@Nullable
|
||||
Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
@Nullable Collection<V> before(Multimap<K, V> multimap, K key) {
|
||||
return Refaster.anyOf(multimap.asMap(), Multimaps.asMap(multimap)).get(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with (possibly) null values. */
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link Optional}s. */
|
||||
@@ -355,6 +355,42 @@ final class OptionalRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when filtering a value of the
|
||||
* former type.
|
||||
*/
|
||||
static final class OptionalFilter<T> {
|
||||
@BeforeTemplate
|
||||
Optional<T> before(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return Refaster.anyOf(
|
||||
optional.stream().filter(predicate).findFirst(),
|
||||
optional.stream().filter(predicate).findAny());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<T> after(Optional<T> optional, Predicate<? super T> predicate) {
|
||||
return optional.filter(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid unnecessary {@link Optional} to {@link Stream} conversion when mapping a value of the
|
||||
* former type.
|
||||
*/
|
||||
// XXX: If `StreamMapFirst` also simplifies `.findAny()` expressions, then this rule can be
|
||||
// dropped in favour of `StreamMapFirst` and `OptionalIdentity`.
|
||||
static final class OptionalMap<S, T> {
|
||||
@BeforeTemplate
|
||||
Optional<? extends T> before(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.stream().map(function).findAny();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Optional<? extends T> after(Optional<S> optional, Function<? super S, ? extends T> function) {
|
||||
return optional.map(function);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Add a rule for:
|
||||
// `optional.flatMap(x -> pred(x) ? Optional.empty() : Optional.of(x))` and variants.
|
||||
// (Maybe canonicalize the inner expression. Maybe we rewrite already.)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkPositionIndex;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster templates related to statements dealing with {@link Preconditions}. */
|
||||
@OnlineDocumentation
|
||||
final class PreconditionsRules {
|
||||
private PreconditionsRules() {}
|
||||
|
||||
/** Prefer {@link Preconditions#checkArgument(boolean)} over more verbose alternatives. */
|
||||
static final class CheckArgument {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
if (condition) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
checkArgument(!condition);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkArgument(boolean, Object)} over more verbose alternatives. */
|
||||
static final class CheckArgumentWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
if (condition) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
checkArgument(!condition, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkElementIndex(int, int, String)} over less descriptive or more
|
||||
* verbose alternatives.
|
||||
*
|
||||
* <p>Note that the two-argument {@link Preconditions#checkElementIndex(int, int)} is better
|
||||
* replaced with {@link java.util.Objects#checkIndex(int, int)}.
|
||||
*/
|
||||
static final class CheckElementIndexWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size, String message) {
|
||||
if (index < 0 || index >= size) {
|
||||
throw new IndexOutOfBoundsException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size, String message) {
|
||||
checkElementIndex(index, size, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkNotNull(Object)} over more verbose alternatives. */
|
||||
static final class CheckNotNull<T> {
|
||||
@BeforeTemplate
|
||||
void before(T object) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(T object) {
|
||||
checkNotNull(object);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkNotNull(Object, Object)} over more verbose alternatives. */
|
||||
static final class CheckNotNullWithMessage<T> {
|
||||
@BeforeTemplate
|
||||
void before(T object, String message) {
|
||||
if (object == null) {
|
||||
throw new NullPointerException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(T object, String message) {
|
||||
checkNotNull(object, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkPositionIndex(int, int)} over less descriptive or more verbose
|
||||
* alternatives.
|
||||
*/
|
||||
static final class CheckPositionIndex {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size) {
|
||||
if (index < 0 || index > size) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size) {
|
||||
checkPositionIndex(index, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Preconditions#checkPositionIndex(int, int, String)} over less descriptive or more
|
||||
* verbose alternatives.
|
||||
*/
|
||||
static final class CheckPositionIndexWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(int index, int size, String message) {
|
||||
if (index < 0 || index > size) {
|
||||
throw new IndexOutOfBoundsException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(int index, int size, String message) {
|
||||
checkPositionIndex(index, size, message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkState(boolean)} over more verbose alternatives. */
|
||||
static final class CheckState {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition) {
|
||||
if (condition) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition) {
|
||||
checkState(!condition);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Preconditions#checkState(boolean, Object)} over more verbose alternatives. */
|
||||
static final class CheckStateWithMessage {
|
||||
@BeforeTemplate
|
||||
void before(boolean condition, String message) {
|
||||
if (condition) {
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
void after(boolean condition, String message) {
|
||||
checkState(!condition, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.MoreCollectors.toOptional;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
|
||||
import static java.util.function.Function.identity;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.MoreCollectors;
|
||||
import com.google.errorprone.refaster.Refaster;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
@@ -14,8 +17,11 @@ import com.google.errorprone.refaster.annotation.NotMatches;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -25,7 +31,11 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.annotation.Description;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.annotation.Severity;
|
||||
import tech.picnic.errorprone.refaster.matchers.ThrowsCheckedException;
|
||||
|
||||
/** Refaster rules related to Reactor expressions and statements. */
|
||||
@@ -68,6 +78,91 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#zip(Mono, Mono)} over a chained {@link Mono#zipWith(Mono)}, as the former
|
||||
* better conveys that the {@link Mono}s may be subscribed to concurrently, and generalizes to
|
||||
* combining three or more reactive streams.
|
||||
*/
|
||||
static final class MonoZip<T, S> {
|
||||
@BeforeTemplate
|
||||
Mono<Tuple2<T, S>> before(Mono<T> mono, Mono<S> other) {
|
||||
return mono.zipWith(other);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<Tuple2<T, S>> after(Mono<T> mono, Mono<S> other) {
|
||||
return Mono.zip(mono, other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#zip(Mono, Mono)} with a chained combinator over a chained {@link
|
||||
* Mono#zipWith(Mono, BiFunction)}, as the former better conveys that the {@link Mono}s may be
|
||||
* subscribed to concurrently, and generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class MonoZipWithCombinator<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Mono<R> before(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
|
||||
return mono.zipWith(other, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<R> after(Mono<T> mono, Mono<S> other, BiFunction<T, S, R> combinator) {
|
||||
return Mono.zip(mono, other).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zip(Publisher, Publisher)} over a chained {@link Flux#zipWith(Publisher)},
|
||||
* as the former better conveys that the {@link Publisher}s may be subscribed to concurrently, and
|
||||
* generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class FluxZip<T, S> {
|
||||
@BeforeTemplate
|
||||
Flux<Tuple2<T, S>> before(Flux<T> flux, Publisher<S> other) {
|
||||
return flux.zipWith(other);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<Tuple2<T, S>> after(Flux<T> flux, Publisher<S> other) {
|
||||
return Flux.zip(flux, other);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zip(Publisher, Publisher)} with a chained combinator over a chained {@link
|
||||
* Flux#zipWith(Publisher, BiFunction)}, as the former better conveys that the {@link Publisher}s
|
||||
* may be subscribed to concurrently, and generalizes to combining three or more reactive streams.
|
||||
*/
|
||||
static final class FluxZipWithCombinator<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWith(other, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<R> after(Flux<T> flux, Publisher<S> other, BiFunction<T, S, R> combinator) {
|
||||
return Flux.zip(flux, other).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#zipWithIterable(Iterable)} with a chained combinator over {@link
|
||||
* Flux#zipWithIterable(Iterable, BiFunction)}, as the former generally yields more readable code.
|
||||
*/
|
||||
static final class FluxZipWithIterable<T, S, R> {
|
||||
@BeforeTemplate
|
||||
Flux<R> before(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWithIterable(iterable, combinator);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
|
||||
Flux<R> after(Flux<T> flux, Iterable<S> iterable, BiFunction<T, S, R> combinator) {
|
||||
return flux.zipWithIterable(iterable).map(function(combinator));
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily defer {@link Mono#error(Throwable)}. */
|
||||
static final class MonoDeferredError<T> {
|
||||
@BeforeTemplate
|
||||
@@ -141,6 +236,34 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#take(long, boolean)} over {@link Flux#take(long)}.
|
||||
*
|
||||
* <p>In Reactor versions prior to 3.5.0, {@code Flux#take(long)} makes an unbounded request
|
||||
* upstream, and is equivalent to {@code Flux#take(long, false)}. In 3.5.0, the behavior of {@code
|
||||
* Flux#take(long)} will change to that of {@code Flux#take(long, true)}.
|
||||
*
|
||||
* <p>The intent with this Refaster rule is to get the new behavior before upgrading to Reactor
|
||||
* 3.5.0.
|
||||
*/
|
||||
// XXX: Drop this rule some time after upgrading to Reactor 3.6.0, or introduce a way to apply
|
||||
// this rule only when an older version of Reactor is on the classpath.
|
||||
// XXX: Once Reactor 3.6.0 is out, introduce a rule that rewrites code in the opposite direction.
|
||||
@Description(
|
||||
"Prior to Reactor 3.5.0, `take(n)` requests and unbounded number of elements upstream.")
|
||||
@Severity(WARNING)
|
||||
static final class FluxTake<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, long n) {
|
||||
return flux.take(n);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, long n) {
|
||||
return flux.take(n, /* limitRequest= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Don't unnecessarily pass an empty publisher to {@link Mono#switchIfEmpty(Mono)}. */
|
||||
static final class MonoSwitchIfEmptyOfEmptyPublisher<T> {
|
||||
@BeforeTemplate
|
||||
@@ -240,16 +363,195 @@ final class ReactorRules {
|
||||
*/
|
||||
abstract static class MonoFlatMapToFlux<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract Mono<S> valueTransformation(@MayOptionallyUse T value);
|
||||
abstract Mono<S> transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Mono<T> mono) {
|
||||
return mono.flatMapMany(v -> valueTransformation(v));
|
||||
return mono.flatMapMany(v -> transformation(v));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Mono<T> mono) {
|
||||
return mono.flatMap(v -> valueTransformation(v)).flux();
|
||||
return mono.flatMap(v -> transformation(v)).flux();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#map(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class MonoMap<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono) {
|
||||
return mono.flatMap(x -> Mono.just(transformation(x)));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Mono<T> mono) {
|
||||
return mono.map(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#map(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class FluxMap<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Flux<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
|
||||
return Refaster.anyOf(
|
||||
flux.concatMap(x -> Mono.just(transformation(x))),
|
||||
flux.concatMap(x -> Flux.just(transformation(x))),
|
||||
flux.concatMap(x -> Mono.just(transformation(x)), prefetch),
|
||||
flux.concatMap(x -> Flux.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x))),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x))),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x)), prefetch),
|
||||
flux.concatMapDelayError(x -> Mono.just(transformation(x)), delayUntilEnd, prefetch),
|
||||
flux.concatMapDelayError(x -> Flux.just(transformation(x)), delayUntilEnd, prefetch),
|
||||
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMap(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMap(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapDelayError(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapDelayError(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency),
|
||||
flux.flatMapSequential(x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequential(x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x -> Mono.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x -> Flux.just(transformation(x)), maxConcurrency, prefetch),
|
||||
flux.switchMap(x -> Mono.just(transformation(x))),
|
||||
flux.switchMap(x -> Flux.just(transformation(x))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux) {
|
||||
return flux.map(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#mapNotNull(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class MonoMapNotNull<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Mono<S> before(Mono<T> mono) {
|
||||
return mono.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)), Mono.fromSupplier(() -> transformation(x))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<S> after(Mono<T> mono) {
|
||||
return mono.mapNotNull(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#mapNotNull(Function)} over alternatives that unnecessarily require an inner
|
||||
* subscription.
|
||||
*/
|
||||
abstract static class FluxMapNotNull<T, S> {
|
||||
@Placeholder(allowsIdentity = true)
|
||||
abstract S transformation(@MayOptionallyUse T value);
|
||||
|
||||
@BeforeTemplate
|
||||
Publisher<S> before(Flux<T> flux, boolean delayUntilEnd, int maxConcurrency, int prefetch) {
|
||||
return Refaster.anyOf(
|
||||
flux.concatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))),
|
||||
flux.concatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
prefetch),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
prefetch),
|
||||
flux.concatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
delayUntilEnd,
|
||||
prefetch),
|
||||
flux.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency),
|
||||
flux.flatMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapSequential(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency),
|
||||
flux.flatMapSequential(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.flatMapSequentialDelayError(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x))),
|
||||
maxConcurrency,
|
||||
prefetch),
|
||||
flux.switchMap(
|
||||
x ->
|
||||
Refaster.anyOf(
|
||||
Mono.justOrEmpty(transformation(x)),
|
||||
Mono.fromSupplier(() -> transformation(x)))));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<S> after(Flux<T> flux) {
|
||||
return flux.mapNotNull(x -> transformation(x));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +651,42 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#doOnError(Class, Consumer)} over {@link Mono#doOnError(Predicate, Consumer)}
|
||||
* where possible.
|
||||
*/
|
||||
static final class MonoDoOnError<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return mono.doOnError(clazz::isInstance, onError);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return mono.doOnError(clazz, onError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#doOnError(Class, Consumer)} over {@link Flux#doOnError(Predicate, Consumer)}
|
||||
* where possible.
|
||||
*/
|
||||
static final class FluxDoOnError<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return flux.doOnError(clazz::isInstance, onError);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux, Class<? extends Throwable> clazz, Consumer<? super Throwable> onError) {
|
||||
return flux.doOnError(clazz, onError);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete()} over more contrived alternatives. */
|
||||
static final class MonoOnErrorComplete<T> {
|
||||
@BeforeTemplate
|
||||
@@ -375,6 +713,240 @@ final class ReactorRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete(Class)}} over more contrived alternatives. */
|
||||
static final class MonoOnErrorCompleteClass<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz) {
|
||||
return Refaster.anyOf(
|
||||
mono.onErrorComplete(clazz::isInstance), mono.onErrorResume(clazz, e -> Mono.empty()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz) {
|
||||
return mono.onErrorComplete(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#onErrorComplete(Class)}} over more contrived alternatives. */
|
||||
static final class FluxOnErrorCompleteClass<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz) {
|
||||
return Refaster.anyOf(
|
||||
flux.onErrorComplete(clazz::isInstance),
|
||||
flux.onErrorResume(clazz, e -> Refaster.anyOf(Mono.empty(), Flux.empty())));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz) {
|
||||
return flux.onErrorComplete(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Mono#onErrorComplete(Predicate)}} over more contrived alternatives. */
|
||||
static final class MonoOnErrorCompletePredicate<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Predicate<? super Throwable> predicate) {
|
||||
return mono.onErrorResume(predicate, e -> Mono.empty());
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Predicate<? super Throwable> predicate) {
|
||||
return mono.onErrorComplete(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Flux#onErrorComplete(Predicate)}} over more contrived alternatives. */
|
||||
static final class FluxOnErrorCompletePredicate<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Predicate<? super Throwable> predicate) {
|
||||
return flux.onErrorResume(predicate, e -> Refaster.anyOf(Mono.empty(), Flux.empty()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Predicate<? super Throwable> predicate) {
|
||||
return flux.onErrorComplete(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorContinue(Class, BiConsumer)} over {@link
|
||||
* Mono#onErrorContinue(Predicate, BiConsumer)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorContinue<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return mono.onErrorContinue(clazz::isInstance, errorConsumer);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return mono.onErrorContinue(clazz, errorConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorContinue(Class, BiConsumer)} over {@link
|
||||
* Flux#onErrorContinue(Predicate, BiConsumer)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorContinue<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return flux.onErrorContinue(clazz::isInstance, errorConsumer);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
BiConsumer<Throwable, Object> errorConsumer) {
|
||||
return flux.onErrorContinue(clazz, errorConsumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorMap(Class, Function)} over {@link Mono#onErrorMap(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorMap<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return mono.onErrorMap(clazz::isInstance, mapper);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return mono.onErrorMap(clazz, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorMap(Class, Function)} over {@link Flux#onErrorMap(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorMap<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return flux.onErrorMap(clazz::isInstance, mapper);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Throwable> mapper) {
|
||||
return flux.onErrorMap(clazz, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorResume(Class, Function)} over {@link Mono#onErrorResume(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorResume<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
|
||||
return mono.onErrorResume(clazz::isInstance, fallback);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(
|
||||
Mono<T> mono,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Mono<? extends T>> fallback) {
|
||||
return mono.onErrorResume(clazz, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorResume(Class, Function)} over {@link Flux#onErrorResume(Predicate,
|
||||
* Function)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorResume<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
|
||||
return flux.onErrorResume(clazz::isInstance, fallback);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(
|
||||
Flux<T> flux,
|
||||
Class<? extends Throwable> clazz,
|
||||
Function<? super Throwable, ? extends Publisher<? extends T>> fallback) {
|
||||
return flux.onErrorResume(clazz, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Mono#onErrorReturn(Class, Object)} over {@link Mono#onErrorReturn(Predicate,
|
||||
* Object)} where possible.
|
||||
*/
|
||||
static final class MonoOnErrorReturn<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return mono.onErrorReturn(clazz::isInstance, fallbackValue);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Mono<T> mono, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return mono.onErrorReturn(clazz, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer {@link Flux#onErrorReturn(Class, Object)} over {@link Flux#onErrorReturn(Predicate,
|
||||
* Object)} where possible.
|
||||
*/
|
||||
static final class FluxOnErrorReturn<T> {
|
||||
@BeforeTemplate
|
||||
Flux<T> before(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return flux.onErrorReturn(clazz::isInstance, fallbackValue);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Flux<T> after(Flux<T> flux, Class<? extends Throwable> clazz, T fallbackValue) {
|
||||
return flux.onErrorReturn(clazz, fallbackValue);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link reactor.util.context.Context#empty()}} over more verbose alternatives. */
|
||||
// XXX: Consider introducing an `IsEmpty` matcher that identifies a wide range of guaranteed-empty
|
||||
// `Collection` and `Map` expressions.
|
||||
static final class ContextEmpty {
|
||||
@BeforeTemplate
|
||||
Context before() {
|
||||
return Context.of(Refaster.anyOf(new HashMap<>(), ImmutableMap.of()));
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Context after() {
|
||||
return Context.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link PublisherProbe#empty()}} over more verbose alternatives. */
|
||||
static final class PublisherProbeEmpty<T> {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -168,8 +168,9 @@ final class StreamRules {
|
||||
* Where possible, clarify that a mapping operation will be applied only to a single stream
|
||||
* element.
|
||||
*/
|
||||
// XXX: Consider whether to have a similar rule for `.findAny()`. For parallel streams it
|
||||
// wouldn't be quite the same....
|
||||
// XXX: Implement a similar rule for `.findAny()`. For parallel streams this wouldn't be quite the
|
||||
// same, so such a rule requires a `Matcher` that heuristically identifies `Stream` expressions
|
||||
// with deterministic order.
|
||||
// XXX: This change is not equivalent for `null`-returning functions, as the original code throws
|
||||
// an NPE if the first element is `null`, while the latter yields an empty `Optional`.
|
||||
static final class StreamMapFirst<T, S> {
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
|
||||
/** Refaster rules related to expressions dealing with {@link String}s. */
|
||||
|
||||
@@ -13,9 +13,11 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.chrono.ChronoLocalDate;
|
||||
import java.time.chrono.ChronoLocalDateTime;
|
||||
import java.time.chrono.ChronoZonedDateTime;
|
||||
@@ -64,7 +66,83 @@ final class TimeRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atOffset(ZoneOffset)} over the more verbose alternative. */
|
||||
/** Prefer {@link LocalDate#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalDateOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalDate before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalDate(),
|
||||
LocalDateTime.ofInstant(instant, zoneId).toLocalDate(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalDate());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalDate before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalDate();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalDate after(Instant instant, ZoneId zoneId) {
|
||||
return LocalDate.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link LocalDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalDateTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalDateTime(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalDateTime());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalDateTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalDateTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return LocalDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link LocalTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class LocalTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
LocalTime before(Instant instant, ZoneId zoneId) {
|
||||
return Refaster.anyOf(
|
||||
instant.atZone(zoneId).toLocalTime(),
|
||||
LocalDateTime.ofInstant(instant, zoneId).toLocalTime(),
|
||||
OffsetDateTime.ofInstant(instant, zoneId).toLocalTime(),
|
||||
OffsetTime.ofInstant(instant, zoneId).toLocalTime());
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
LocalTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toLocalTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
LocalTime after(Instant instant, ZoneId zoneId) {
|
||||
return LocalTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link OffsetDateTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class OffsetDateTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
OffsetDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return instant.atZone(zoneId).toOffsetDateTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
OffsetDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return OffsetDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atOffset(ZoneOffset)} over more verbose alternatives. */
|
||||
static final class InstantAtOffset {
|
||||
@BeforeTemplate
|
||||
OffsetDateTime before(Instant instant, ZoneOffset zoneOffset) {
|
||||
@@ -77,6 +155,37 @@ final class TimeRules {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link OffsetTime#ofInstant(Instant, ZoneId)} over more indirect alternatives. */
|
||||
static final class OffsetTimeOfInstant {
|
||||
@BeforeTemplate
|
||||
OffsetTime before(Instant instant, ZoneId zoneId) {
|
||||
return OffsetDateTime.ofInstant(instant, zoneId).toOffsetTime();
|
||||
}
|
||||
|
||||
@BeforeTemplate
|
||||
OffsetTime before(Instant instant, ZoneOffset zoneId) {
|
||||
return instant.atOffset(zoneId).toOffsetTime();
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
OffsetTime after(Instant instant, ZoneId zoneId) {
|
||||
return OffsetTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prefer {@link Instant#atZone(ZoneId)} over more verbose alternatives. */
|
||||
static final class InstantAtZone {
|
||||
@BeforeTemplate
|
||||
ZonedDateTime before(Instant instant, ZoneId zoneId) {
|
||||
return ZonedDateTime.ofInstant(instant, zoneId);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
ZonedDateTime after(Instant instant, ZoneId zoneId) {
|
||||
return instant.atZone(zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Use {@link Clock#systemUTC()} when possible. */
|
||||
static final class UtcClock {
|
||||
@BeforeTemplate
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Picnic Refaster rules. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
@@ -65,6 +65,23 @@ final class CollectorMutabilityTest {
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithoutGuavaOnClasspath() {
|
||||
compilationTestHelper
|
||||
.withClasspath()
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Collectors;",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.empty().collect(Collectors.toList());",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementFirstSuggestedFix() {
|
||||
refactoringTestHelper
|
||||
|
||||
@@ -4,6 +4,7 @@ import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -33,6 +34,14 @@ final class FluxFlatMapUsageTest {
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).<String>flatMap(i -> Flux.just(String.valueOf(i)));",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Flux.just(1, 2).groupBy(i -> i).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
|
||||
"",
|
||||
" Mono.just(1).flatMap(Mono::just);",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
@@ -71,9 +80,13 @@ final class FluxFlatMapUsageTest {
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final int MAX_CONCURRENCY = 8;",
|
||||
"",
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
@@ -81,12 +94,16 @@ final class FluxFlatMapUsageTest {
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"class A {",
|
||||
" private static final int MAX_CONCURRENCY = 8;",
|
||||
"",
|
||||
" void m() {",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
" Flux.just(1).concatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,6 +120,8 @@ final class FluxFlatMapUsageTest {
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
@@ -115,8 +134,10 @@ final class FluxFlatMapUsageTest {
|
||||
" void m() {",
|
||||
" Flux.just(1).flatMap(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
|
||||
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class IsInstanceLambdaUsageTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Stream.of(1).filter(i -> i instanceof Integer);",
|
||||
" Stream.of(2).filter(Integer.class::isInstance);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).filter(i -> i instanceof Integer);",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import java.util.stream.Stream;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
" Stream.of(1).filter(Integer.class::isInstance);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
@@ -9,9 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
final class LexicographicalAnnotationListingTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"X", containsPattern("Sort annotations lexicographically where possible"));
|
||||
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
LexicographicalAnnotationListing.class, getClass());
|
||||
@@ -21,7 +17,9 @@ final class LexicographicalAnnotationListingTest {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.lang.annotation.ElementType;",
|
||||
"import java.lang.annotation.Repeatable;",
|
||||
"import java.lang.annotation.Target;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Repeatable(Foos.class)",
|
||||
@@ -33,6 +31,7 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.METHOD)",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
@@ -45,11 +44,21 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Foo[] value();",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface FooTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface BarTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo",
|
||||
" @Bar",
|
||||
" A unsortedSimpleCase();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo()",
|
||||
" @Bar()",
|
||||
" A unsortedWithParens();",
|
||||
@@ -61,12 +70,12 @@ final class LexicographicalAnnotationListingTest {
|
||||
" @Foo()",
|
||||
" A sortedAnnotationsOneWithParens();",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo",
|
||||
" @Baz",
|
||||
" @Bar",
|
||||
" A threeUnsortedAnnotationsSameInitialLetter();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Bar",
|
||||
" @Foo()",
|
||||
" @Baz",
|
||||
@@ -77,16 +86,16 @@ final class LexicographicalAnnotationListingTest {
|
||||
" @Foo()",
|
||||
" A threeSortedAnnotations();",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({\"b\"})",
|
||||
" @Bar({\"a\"})",
|
||||
" A unsortedWithStringAttributes();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Baz(str = {\"a\", \"b\"})",
|
||||
" @Foo(ints = {1, 0})",
|
||||
" @Bar",
|
||||
" A unsortedWithAttributes();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Bar",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
|
||||
" @Baz",
|
||||
@@ -101,11 +110,23 @@ final class LexicographicalAnnotationListingTest {
|
||||
" @Foo(ints = {1, 2})",
|
||||
" @Foo({\"b\"})",
|
||||
" A sortedRepeatableAnnotation();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
|
||||
" @Bar",
|
||||
" @Foo(ints = {1, 2})",
|
||||
" A unsortedRepeatableAnnotation();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" default @FooTypeUse @BarTypeUse A unsortedTypeAnnotations() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Baz",
|
||||
" @Bar",
|
||||
" default @FooTypeUse @BarTypeUse A unsortedTypeUseAndOtherAnnotations() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@@ -115,7 +136,9 @@ final class LexicographicalAnnotationListingTest {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import java.lang.annotation.ElementType;",
|
||||
"import java.lang.annotation.Repeatable;",
|
||||
"import java.lang.annotation.Target;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Repeatable(Foos.class)",
|
||||
@@ -127,6 +150,7 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.METHOD)",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
@@ -139,6 +163,16 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Foo[] value();",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface FooTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface BarTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Bar",
|
||||
" A singleAnnotation();",
|
||||
"",
|
||||
@@ -174,10 +208,18 @@ final class LexicographicalAnnotationListingTest {
|
||||
" @Bar",
|
||||
" @Foo(ints = {1, 2})",
|
||||
" A unsortedRepeatableAnnotation();",
|
||||
"",
|
||||
" @Baz",
|
||||
" @Bar",
|
||||
" default @FooTypeUse @BarTypeUse A unsortedWithTypeUseAnnotations() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import java.lang.annotation.ElementType;",
|
||||
"import java.lang.annotation.Repeatable;",
|
||||
"import java.lang.annotation.Target;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Repeatable(Foos.class)",
|
||||
@@ -189,6 +231,7 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.METHOD)",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
@@ -201,6 +244,16 @@ final class LexicographicalAnnotationListingTest {
|
||||
" Foo[] value();",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface FooTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Target(ElementType.TYPE_USE)",
|
||||
" @interface BarTypeUse {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Bar",
|
||||
" A singleAnnotation();",
|
||||
"",
|
||||
@@ -236,6 +289,12 @@ final class LexicographicalAnnotationListingTest {
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")})",
|
||||
" @Foo(ints = {1, 2})",
|
||||
" A unsortedRepeatableAnnotation();",
|
||||
"",
|
||||
" @Bar",
|
||||
" @Baz",
|
||||
" default @BarTypeUse @FooTypeUse A unsortedWithTypeUseAnnotations() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ final class RequestParamTypeTest {
|
||||
"import java.util.List;",
|
||||
"import java.util.Map;",
|
||||
"import java.util.Set;",
|
||||
"import javax.annotation.Nullable;",
|
||||
"import org.jspecify.nullness.Nullable;",
|
||||
"import org.springframework.web.bind.annotation.DeleteMapping;",
|
||||
"import org.springframework.web.bind.annotation.GetMapping;",
|
||||
"import org.springframework.web.bind.annotation.PostMapping;",
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
final class ScheduledTransactionTraceTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
@@ -43,6 +44,21 @@ final class ScheduledTransactionTraceTest {
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationWithoutNewRelicAgentApiOnClasspath() {
|
||||
compilationTestHelper
|
||||
.withClasspath(Scheduled.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"class A {",
|
||||
" @Scheduled(fixedDelay = 1)",
|
||||
" void scheduledButNotTraced() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public final class SimplifyTimeAnnotationCheckTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(SimplifyTimeAnnotationCheck.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(SimplifyTimeAnnotationCheck.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import java.util.concurrent.TimeUnit;",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Timeout(6)",
|
||||
" A noSimplification();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Timeout(60)",
|
||||
" A simple();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Timeout(value = 60 * 1000, unit = TimeUnit.MILLISECONDS)",
|
||||
" A explicitUnit();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void identificationBannedField() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"interface A {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Scheduled(fixedDelay = 6_000)",
|
||||
" A scheduledFixedDelay();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 6_000, fixedRateString = \"\")",
|
||||
" A bannedAttribute();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/A.java",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Timeout(value = 60)",
|
||||
" A simple();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 6_000)",
|
||||
" A scheduledFixedDelay();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 5_000, initialDelay = 6_000, fixedRate = 7_000)",
|
||||
" A scheduledMultiple();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 60_000, initialDelay = 6_000, fixedRate = 7_000)",
|
||||
" A scheduledCommonUnit();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 5, initialDelay = 6_000, fixedRate = 7_000)",
|
||||
" A scheduledNoSimplification();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/A.java",
|
||||
"import static java.util.concurrent.TimeUnit.MINUTES;",
|
||||
"import static java.util.concurrent.TimeUnit.SECONDS;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"import org.springframework.scheduling.annotation.Scheduled;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Timeout(value = 1, unit = MINUTES)",
|
||||
" A simple();",
|
||||
"",
|
||||
" @Scheduled(timeUnit = SECONDS, fixedDelay = 6)",
|
||||
" A scheduledFixedDelay();",
|
||||
"",
|
||||
" @Scheduled(timeUnit = SECONDS, fixedDelay = 5, initialDelay = 6, fixedRate = 7)",
|
||||
" A scheduledMultiple();",
|
||||
"",
|
||||
" @Scheduled(timeUnit = SECONDS, fixedDelay = 60, initialDelay = 6, fixedRate = 7)",
|
||||
" A scheduledCommonUnit();",
|
||||
"",
|
||||
" @Scheduled(fixedDelay = 5, initialDelay = 6_000, fixedRate = 7_000)",
|
||||
" A scheduledNoSimplification();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementValueOnly() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/A.java",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Timeout(60)",
|
||||
" A simple();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/A.java",
|
||||
"import static java.util.concurrent.TimeUnit.MINUTES;",
|
||||
"",
|
||||
"import org.junit.jupiter.api.Timeout;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @Timeout(value = 1, unit = MINUTES)",
|
||||
" A simple();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementFqcn() {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/A.java",
|
||||
"interface A {",
|
||||
" @org.junit.jupiter.api.Timeout(60)",
|
||||
" A simple();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/A.java",
|
||||
"import static java.util.concurrent.TimeUnit.MINUTES;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @org.junit.jupiter.api.Timeout(value = 1, unit = MINUTES)",
|
||||
" A simple();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ final class SpringMvcAnnotationTest {
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.DELETE;",
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.GET;",
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.HEAD;",
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.PATCH;",
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.POST;",
|
||||
"import static org.springframework.web.bind.annotation.RequestMethod.PUT;",
|
||||
"",
|
||||
@@ -50,7 +49,7 @@ final class SpringMvcAnnotationTest {
|
||||
" @RequestMapping(method = {DELETE})",
|
||||
" A delete();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @RequestMapping(method = {PATCH})",
|
||||
" @RequestMapping(method = {org.springframework.web.bind.annotation.RequestMethod.PATCH})",
|
||||
" A patch();",
|
||||
"",
|
||||
" @RequestMapping(method = HEAD)",
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Predicates.containsPattern;
|
||||
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class TimeZoneUsageTest {
|
||||
private final CompilationTestHelper compilationHelper =
|
||||
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass())
|
||||
.expectErrorMessage(
|
||||
"X",
|
||||
containsPattern(
|
||||
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone"));
|
||||
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass());
|
||||
|
||||
@Test
|
||||
void identification() {
|
||||
@@ -26,7 +20,10 @@ final class TimeZoneUsageTest {
|
||||
"import java.time.LocalDate;",
|
||||
"import java.time.LocalDateTime;",
|
||||
"import java.time.LocalTime;",
|
||||
"import java.time.OffsetDateTime;",
|
||||
"import java.time.OffsetTime;",
|
||||
"import java.time.ZoneId;",
|
||||
"import java.time.ZonedDateTime;",
|
||||
"",
|
||||
"class A {",
|
||||
" void m() {",
|
||||
@@ -36,48 +33,69 @@ final class TimeZoneUsageTest {
|
||||
" Clock.offset(clock, Duration.ZERO);",
|
||||
" Clock.tick(clock, Duration.ZERO);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.systemUTC();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.systemDefaultZone();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.system(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickMillis(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickMinutes(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Clock.tickSeconds(UTC);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" clock.getZone();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" clock.withZone(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Instant.now();",
|
||||
" // This is equivalent to `clock.instant()`, which is fine.",
|
||||
" Instant.now(clock);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDate.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalDateTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now();",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now(clock);",
|
||||
" // BUG: Diagnostic matches: X",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" LocalTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetDateTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" OffsetTime.now(UTC);",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now(clock);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" ZonedDateTime.now(UTC);",
|
||||
" }",
|
||||
"",
|
||||
" abstract class ForwardingClock extends Clock {",
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.superOf;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.suppliers.Supplier;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.Signatures;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class MoreTypesTest {
|
||||
private static final ImmutableSet<Supplier<Type>> TYPES =
|
||||
ImmutableSet.of(
|
||||
// Invalid types.
|
||||
type("java.lang.Nonexistent"),
|
||||
generic(type("java.util.Integer"), unbound()),
|
||||
// Valid types.
|
||||
type("java.lang.String"),
|
||||
type("java.lang.Number"),
|
||||
superOf(type("java.lang.Number")),
|
||||
subOf(type("java.lang.Number")),
|
||||
type("java.lang.Integer"),
|
||||
superOf(type("java.lang.Integer")),
|
||||
subOf(type("java.lang.Integer")),
|
||||
type("java.util.Optional"),
|
||||
raw(type("java.util.Optional")),
|
||||
generic(type("java.util.Optional"), unbound()),
|
||||
generic(type("java.util.Optional"), type("java.lang.Number")),
|
||||
type("java.util.Collection"),
|
||||
raw(type("java.util.Collection")),
|
||||
generic(type("java.util.Collection"), unbound()),
|
||||
generic(type("java.util.Collection"), type("java.lang.Number")),
|
||||
generic(type("java.util.Collection"), superOf(type("java.lang.Number"))),
|
||||
generic(type("java.util.Collection"), subOf(type("java.lang.Number"))),
|
||||
generic(type("java.util.Collection"), type("java.lang.Integer")),
|
||||
generic(type("java.util.Collection"), superOf(type("java.lang.Integer"))),
|
||||
generic(type("java.util.Collection"), subOf(type("java.lang.Integer"))),
|
||||
type("java.util.List"),
|
||||
raw(type("java.util.List")),
|
||||
generic(type("java.util.List"), unbound()),
|
||||
generic(type("java.util.List"), type("java.lang.Number")),
|
||||
generic(type("java.util.List"), superOf(type("java.lang.Number"))),
|
||||
generic(type("java.util.List"), subOf(type("java.lang.Number"))),
|
||||
generic(type("java.util.List"), type("java.lang.Integer")),
|
||||
generic(type("java.util.List"), superOf(type("java.lang.Integer"))),
|
||||
generic(type("java.util.List"), subOf(type("java.lang.Integer"))),
|
||||
generic(
|
||||
type("java.util.Map"),
|
||||
type("java.lang.String"),
|
||||
subOf(generic(type("java.util.Collection"), superOf(type("java.lang.Short"))))));
|
||||
|
||||
@Test
|
||||
void matcher() {
|
||||
CompilationTestHelper.newInstance(SubtypeFlagger.class, getClass())
|
||||
.addSourceLines(
|
||||
"/A.java",
|
||||
"import java.util.Collection;",
|
||||
"import java.util.List;",
|
||||
"import java.util.Map;",
|
||||
"import java.util.Optional;",
|
||||
"import java.util.Set;",
|
||||
"",
|
||||
"class A<S, T> {",
|
||||
" void m() {",
|
||||
" Object object = factory();",
|
||||
" A a = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Number, ? super Number, Integer, ? super Integer]",
|
||||
" int integer = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [String]",
|
||||
" String string = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Optional]",
|
||||
" Optional rawOptional = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<S> optionalOfS = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<T> optionalOfT = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>, Optional<Number>]",
|
||||
" Optional<Number> optionalOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
|
||||
" Optional<Integer> optionalOfInteger = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection]",
|
||||
" Collection rawCollection = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
|
||||
" Collection<Number> collectionOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
|
||||
" Collection<Integer> collectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
|
||||
" Collection<Short> collectionOfShort = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection, List]",
|
||||
" List rawList = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>, List, List<?>,",
|
||||
" // List<Number>, List<? super Number>, List<? extends Number>, List<? super Integer>]",
|
||||
" List<Number> listOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>, List,",
|
||||
" // List<?>, List<? extends Number>, List<Integer>, List<? super Integer>, List<? extends",
|
||||
" // Integer>]",
|
||||
" List<Integer> listOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>, List,",
|
||||
" // List<?>, List<? extends Number>]",
|
||||
" List<Short> listOfShort = factory();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: [Collection]",
|
||||
" Set rawSet = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
|
||||
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
|
||||
" Set<Number> setOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
|
||||
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
|
||||
" Set<Integer> setOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
|
||||
" Set<Short> setOfShort = factory();",
|
||||
"",
|
||||
" Map rawMap = factory();",
|
||||
" Map<Number, Collection<Number>> mapFromNumberToCollectionOfNumber = factory();",
|
||||
" Map<Number, Collection<Short>> mapFromNumberToCollectionOfShort = factory();",
|
||||
" Map<Number, Collection<Integer>> mapFromNumberToCollectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, Collection<Number>> mapFromStringToCollectionOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, Collection<Short>> mapFromStringToCollectionOfShort = factory();",
|
||||
" Map<String, Collection<Integer>> mapFromStringToCollectionOfInteger = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, List<Number>> mapFromStringToListOfNumber = factory();",
|
||||
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
|
||||
" Map<String, List<Short>> mapFromStringToListOfShort = factory();",
|
||||
" Map<String, List<Integer>> mapFromStringToListOfInteger = factory();",
|
||||
" }",
|
||||
"",
|
||||
" private <T> T factory() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that flags method invocations that are a subtype of any type contained in
|
||||
* {@link #TYPES}.
|
||||
*/
|
||||
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
|
||||
public static final class SubtypeFlagger extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
Type treeType = ASTHelpers.getType(tree);
|
||||
|
||||
List<String> matches = new ArrayList<>();
|
||||
|
||||
for (Supplier<Type> type : TYPES) {
|
||||
Type testType = type.get(state);
|
||||
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
|
||||
matches.add(Signatures.prettyType(testType));
|
||||
}
|
||||
}
|
||||
|
||||
return matches.isEmpty()
|
||||
? Description.NO_MATCH
|
||||
: buildDescription(tree).setMessage(matches.toString()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
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.sun.source.tree.ClassTree;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
final class ThirdPartyLibraryTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(TestChecker.class, getClass());
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowed() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesInSymtab() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.common.collect.ImmutableList;",
|
||||
"import com.newrelic.api.agent.Agent;",
|
||||
"import org.assertj.core.api.Assertions;",
|
||||
"import reactor.core.publisher.Flux;",
|
||||
"",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: true, GUAVA: true, NEW_RELIC_AGENT_API: true, REACTOR: true",
|
||||
"class A {",
|
||||
" void m(Class<?> clazz) {",
|
||||
" m(Assertions.class);",
|
||||
" m(ImmutableList.class);",
|
||||
" m(Agent.class);",
|
||||
" m(Flux.class);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesPartiallyOnClassPath() {
|
||||
compilationTestHelper
|
||||
.withClasspath(ImmutableList.class, Flux.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: true, NEW_RELIC_AGENT_API: false, REACTOR: true",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntroductionAllowedWitnessClassesNotOnClassPath() {
|
||||
compilationTestHelper
|
||||
.withClasspath()
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"// BUG: Diagnostic contains: ASSERTJ: false, GUAVA: false, NEW_RELIC_AGENT_API: false, REACTOR:",
|
||||
"// false",
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void isIntroductionAllowedIgnoreClasspathCompat(boolean ignoreClassPath) {
|
||||
compilationTestHelper
|
||||
.setArgs("-XepOpt:ErrorProneSupport:IgnoreClasspathCompat=" + ignoreClassPath)
|
||||
.withClasspath(ImmutableList.class, Flux.class)
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
String.format(
|
||||
"// BUG: Diagnostic contains: ASSERTJ: %s, GUAVA: true, NEW_RELIC_AGENT_API: %s, REACTOR: true",
|
||||
ignoreClassPath, ignoreClassPath),
|
||||
"class A {}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags classes with a diagnostics message that indicates, for each {@link ThirdPartyLibrary}
|
||||
* element, whether they can be used.
|
||||
*/
|
||||
@BugPattern(severity = ERROR, summary = "Interacts with `ThirdPartyLibrary` for testing purposes")
|
||||
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
return buildDescription(tree)
|
||||
.setMessage(
|
||||
Arrays.stream(ThirdPartyLibrary.values())
|
||||
.map(
|
||||
lib ->
|
||||
String.join(
|
||||
": ", lib.name(), String.valueOf(lib.isIntroductionAllowed(state))))
|
||||
.collect(joining(", ")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@ final class RefasterRulesTest {
|
||||
MultimapRules.class,
|
||||
NullRules.class,
|
||||
OptionalRules.class,
|
||||
PreconditionsRules.class,
|
||||
PrimitiveRules.class,
|
||||
ReactorRules.class,
|
||||
RxJava2AdapterRules.class,
|
||||
|
||||
@@ -37,6 +37,12 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Preconditions.checkElementIndex(0, 1);
|
||||
}
|
||||
|
||||
void testCheckIndexConditional() {
|
||||
if (1 < 0 || 1 >= 2) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
Map<RoundingMode, String> testCreateEnumMap() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.toImmutableEnumSet;
|
||||
import static java.util.Objects.checkIndex;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
@@ -19,7 +20,6 @@ import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
@@ -38,7 +38,11 @@ final class AssortedRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
int testCheckIndex() {
|
||||
return Objects.checkIndex(0, 1);
|
||||
return checkIndex(0, 1);
|
||||
}
|
||||
|
||||
void testCheckIndexConditional() {
|
||||
checkIndex(1, 2);
|
||||
}
|
||||
|
||||
Map<RoundingMode, String> testCreateEnumMap() {
|
||||
|
||||
@@ -109,4 +109,14 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Optional.of("baz").stream().min(String::compareTo),
|
||||
Optional.of("qux").stream().max(String::compareTo));
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<String>> testOptionalFilter() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").stream().filter(String::isEmpty).findFirst(),
|
||||
Optional.of("bar").stream().filter(String::isEmpty).findAny());
|
||||
}
|
||||
|
||||
Optional<String> testOptionalMap() {
|
||||
return Optional.of(1).stream().map(String::valueOf).findAny();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,4 +103,13 @@ final class OptionalRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo"), Optional.of("bar"), Optional.of("baz"), Optional.of("qux"));
|
||||
}
|
||||
|
||||
ImmutableSet<Optional<String>> testOptionalFilter() {
|
||||
return ImmutableSet.of(
|
||||
Optional.of("foo").filter(String::isEmpty), Optional.of("bar").filter(String::isEmpty));
|
||||
}
|
||||
|
||||
Optional<String> testOptionalMap() {
|
||||
return Optional.of(1).map(String::valueOf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PreconditionsRulesTest implements RefasterRuleCollectionTestCase {
|
||||
void testCheckArgument() {
|
||||
if ("foo".isEmpty()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckArgumentWithMessage() {
|
||||
if ("foo".isEmpty()) {
|
||||
throw new IllegalArgumentException("The string is empty");
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckElementIndexWithMessage() {
|
||||
if (1 < 0 || 1 >= 2) {
|
||||
throw new IndexOutOfBoundsException("My index");
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckNotNull() {
|
||||
if ("foo" == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckNotNullWithMessage() {
|
||||
if ("foo" == null) {
|
||||
throw new NullPointerException("The string is null");
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckPositionIndex() {
|
||||
if (1 < 0 || 1 > 2) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckPositionIndexWithMessage() {
|
||||
if (1 < 0 || 1 > 2) {
|
||||
throw new IndexOutOfBoundsException("My position");
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckState() {
|
||||
if ("foo".isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
void testCheckStateWithMessage() {
|
||||
if ("foo".isEmpty()) {
|
||||
throw new IllegalStateException("The string is empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package tech.picnic.errorprone.refasterrules;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkElementIndex;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkPositionIndex;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class PreconditionsRulesTest implements RefasterRuleCollectionTestCase {
|
||||
void testCheckArgument() {
|
||||
checkArgument(!"foo".isEmpty());
|
||||
}
|
||||
|
||||
void testCheckArgumentWithMessage() {
|
||||
checkArgument(!"foo".isEmpty(), "The string is empty");
|
||||
}
|
||||
|
||||
void testCheckElementIndexWithMessage() {
|
||||
checkElementIndex(1, 2, "My index");
|
||||
}
|
||||
|
||||
void testCheckNotNull() {
|
||||
checkNotNull("foo");
|
||||
}
|
||||
|
||||
void testCheckNotNullWithMessage() {
|
||||
checkNotNull("foo", "The string is null");
|
||||
}
|
||||
|
||||
void testCheckPositionIndex() {
|
||||
checkPositionIndex(1, 2);
|
||||
}
|
||||
|
||||
void testCheckPositionIndexWithMessage() {
|
||||
checkPositionIndex(1, 2, "My position");
|
||||
}
|
||||
|
||||
void testCheckState() {
|
||||
checkState(!"foo".isEmpty());
|
||||
}
|
||||
|
||||
void testCheckStateWithMessage() {
|
||||
checkState(!"foo".isEmpty(), "The string is empty");
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
@@ -12,12 +14,14 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<?> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(assertThat(0));
|
||||
return ImmutableSet.of(assertThat(0), HashMap.class, ImmutableMap.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<?>> testMonoFromSupplier() {
|
||||
@@ -35,6 +39,26 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Mono.fromSupplier(() -> Optional.of(2).orElse(null)));
|
||||
}
|
||||
|
||||
Mono<Tuple2<String, Integer>> testMonoZip() {
|
||||
return Mono.just("foo").zipWith(Mono.just(1));
|
||||
}
|
||||
|
||||
Mono<String> testMonoZipWithCombinator() {
|
||||
return Mono.just("foo").zipWith(Mono.just(1), String::repeat);
|
||||
}
|
||||
|
||||
Flux<Tuple2<String, Integer>> testFluxZip() {
|
||||
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithCombinator() {
|
||||
return Flux.just("foo", "bar").zipWith(Flux.just(1, 2), String::repeat);
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithIterable() {
|
||||
return Flux.just("foo", "bar").zipWithIterable(ImmutableSet.of(1, 2), String::repeat);
|
||||
}
|
||||
|
||||
Mono<Void> testMonoDeferredError() {
|
||||
return Mono.defer(() -> Mono.error(new IllegalStateException()));
|
||||
}
|
||||
@@ -55,6 +79,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.empty().then(Mono.just("foo"));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxTake() {
|
||||
return Flux.just(1, 2, 3).take(1);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
|
||||
return Mono.just(1).switchIfEmpty(Mono.empty());
|
||||
}
|
||||
@@ -83,7 +111,71 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlatMapToFlux() {
|
||||
return Mono.just("foo").flatMapMany(s -> Mono.just(s + s));
|
||||
return Mono.just("foo").flatMapMany(s -> Mono.fromSupplier(() -> s + s));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMap() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").flatMap(s -> Mono.just(s)),
|
||||
Mono.just("bar").flatMap(s -> Mono.just(s.substring(1))));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMap() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).concatMap(n -> Mono.just(n)),
|
||||
Flux.just(1).concatMap(n -> Flux.just(n * 2)),
|
||||
Flux.just(1).concatMap(n -> Mono.just(n), 3),
|
||||
Flux.just(1).concatMap(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.just(n)),
|
||||
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.just(n), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.just(n), 3),
|
||||
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMap(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.just(n), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Flux.just(n * 2), 3, 4),
|
||||
Flux.just(1).switchMap(n -> Mono.just(n)),
|
||||
Flux.just(1).switchMap(n -> Flux.just(n * 2)));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").flatMap(s -> Mono.justOrEmpty(s)),
|
||||
Mono.just("bar").flatMap(s -> Mono.fromSupplier(() -> s.substring(1))));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n)),
|
||||
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2)),
|
||||
Flux.just(1).concatMap(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).concatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2)),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).concatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMap(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMap(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapSequential(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.justOrEmpty(n), 3, 4),
|
||||
Flux.just(1).flatMapSequentialDelayError(n -> Mono.fromSupplier(() -> n * 2), 3, 4),
|
||||
Flux.just(1).switchMap(n -> Mono.justOrEmpty(n)),
|
||||
Flux.just(1).switchMap(n -> Mono.fromSupplier(() -> n * 2)));
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlux() {
|
||||
@@ -116,6 +208,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMap(Flux::fromIterable, 2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoDoOnError() {
|
||||
return Mono.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxDoOnError() {
|
||||
return Flux.just(1).doOnError(IllegalArgumentException.class::isInstance, e -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorComplete() {
|
||||
return Mono.just(1).onErrorResume(e -> Mono.empty());
|
||||
}
|
||||
@@ -126,6 +226,67 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(2).onErrorResume(e -> Flux.empty()));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
|
||||
Mono.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(IllegalArgumentException.class::isInstance),
|
||||
Flux.just(2).onErrorResume(IllegalStateException.class, e -> Mono.empty()),
|
||||
Flux.just(3).onErrorResume(AssertionError.class, e -> Flux.empty()));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorCompletePredicate() {
|
||||
return Mono.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty());
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorResume(e -> e.getCause() == null, e -> Mono.empty()),
|
||||
Flux.just(2).onErrorResume(e -> e.getCause() != null, e -> Flux.empty()));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorContinue() {
|
||||
return Mono.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorContinue() {
|
||||
return Flux.just(1).onErrorContinue(IllegalArgumentException.class::isInstance, (e, v) -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorMap() {
|
||||
return Mono.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorMap() {
|
||||
return Flux.just(1).onErrorMap(IllegalArgumentException.class::isInstance, e -> e);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorResume() {
|
||||
return Mono.just(1)
|
||||
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Mono.just(2));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorResume() {
|
||||
return Flux.just(1)
|
||||
.onErrorResume(IllegalArgumentException.class::isInstance, e -> Flux.just(2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorReturn() {
|
||||
return Mono.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorReturn() {
|
||||
return Flux.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Context> testContextEmpty() {
|
||||
return ImmutableSet.of(Context.of(new HashMap<>()), Context.of(ImmutableMap.of()));
|
||||
}
|
||||
|
||||
ImmutableSet<PublisherProbe<Void>> testPublisherProbeEmpty() {
|
||||
return ImmutableSet.of(PublisherProbe.of(Mono.empty()), PublisherProbe.of(Flux.empty()));
|
||||
}
|
||||
|
||||
@@ -3,23 +3,29 @@ package tech.picnic.errorprone.refasterrules;
|
||||
import static com.google.common.collect.MoreCollectors.toOptional;
|
||||
import static java.util.function.Function.identity;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static reactor.function.TupleUtils.function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Supplier;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.function.TupleUtils;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.publisher.PublisherProbe;
|
||||
import reactor.util.context.Context;
|
||||
import reactor.util.function.Tuple2;
|
||||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
|
||||
|
||||
final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
@Override
|
||||
public ImmutableSet<?> elidedTypesAndStaticImports() {
|
||||
return ImmutableSet.of(assertThat(0));
|
||||
return ImmutableSet.of(assertThat(0), HashMap.class, ImmutableMap.class);
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<?>> testMonoFromSupplier() {
|
||||
@@ -37,6 +43,29 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Mono.defer(() -> Mono.justOrEmpty(Optional.of(2))));
|
||||
}
|
||||
|
||||
Mono<Tuple2<String, Integer>> testMonoZip() {
|
||||
return Mono.zip(Mono.just("foo"), Mono.just(1));
|
||||
}
|
||||
|
||||
Mono<String> testMonoZipWithCombinator() {
|
||||
return Mono.zip(Mono.just("foo"), Mono.just(1)).map(TupleUtils.function(String::repeat));
|
||||
}
|
||||
|
||||
Flux<Tuple2<String, Integer>> testFluxZip() {
|
||||
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithCombinator() {
|
||||
return Flux.zip(Flux.just("foo", "bar"), Flux.just(1, 2))
|
||||
.map(TupleUtils.function(String::repeat));
|
||||
}
|
||||
|
||||
Flux<String> testFluxZipWithIterable() {
|
||||
return Flux.just("foo", "bar")
|
||||
.zipWithIterable(ImmutableSet.of(1, 2))
|
||||
.map(function(String::repeat));
|
||||
}
|
||||
|
||||
Mono<Void> testMonoDeferredError() {
|
||||
return Mono.error(() -> new IllegalStateException());
|
||||
}
|
||||
@@ -57,6 +86,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return Mono.empty().thenReturn("foo");
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxTake() {
|
||||
return Flux.just(1, 2, 3).take(1, true);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
|
||||
return Mono.just(1);
|
||||
}
|
||||
@@ -83,7 +116,68 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlatMapToFlux() {
|
||||
return Mono.just("foo").flatMap(s -> Mono.just(s + s)).flux();
|
||||
return Mono.just("foo").flatMap(s -> Mono.fromSupplier(() -> s + s)).flux();
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMap() {
|
||||
return ImmutableSet.of(Mono.just("foo").map(s -> s), Mono.just("bar").map(s -> s.substring(1)));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMap() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2),
|
||||
Flux.just(1).map(n -> n),
|
||||
Flux.just(1).map(n -> n * 2));
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<String>> testMonoMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just("foo").mapNotNull(s -> s), Mono.just("bar").mapNotNull(s -> s.substring(1)));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxMapNotNull() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2),
|
||||
Flux.just(1).mapNotNull(n -> n),
|
||||
Flux.just(1).mapNotNull(n -> n * 2));
|
||||
}
|
||||
|
||||
Flux<String> testMonoFlux() {
|
||||
@@ -116,6 +210,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
Flux.just(ImmutableList.of("bar")).concatMapIterable(identity(), 2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoDoOnError() {
|
||||
return Mono.just(1).doOnError(IllegalArgumentException.class, e -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxDoOnError() {
|
||||
return Flux.just(1).doOnError(IllegalArgumentException.class, e -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorComplete() {
|
||||
return Mono.just(1).onErrorComplete();
|
||||
}
|
||||
@@ -124,6 +226,65 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
|
||||
return ImmutableSet.of(Flux.just(1).onErrorComplete(), Flux.just(2).onErrorComplete());
|
||||
}
|
||||
|
||||
ImmutableSet<Mono<Integer>> testMonoOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Mono.just(1).onErrorComplete(IllegalArgumentException.class),
|
||||
Mono.just(2).onErrorComplete(IllegalStateException.class));
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompleteClass() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(IllegalArgumentException.class),
|
||||
Flux.just(2).onErrorComplete(IllegalStateException.class),
|
||||
Flux.just(3).onErrorComplete(AssertionError.class));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorCompletePredicate() {
|
||||
return Mono.just(1).onErrorComplete(e -> e.getCause() == null);
|
||||
}
|
||||
|
||||
ImmutableSet<Flux<Integer>> testFluxOnErrorCompletePredicate() {
|
||||
return ImmutableSet.of(
|
||||
Flux.just(1).onErrorComplete(e -> e.getCause() == null),
|
||||
Flux.just(2).onErrorComplete(e -> e.getCause() != null));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorContinue() {
|
||||
return Mono.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorContinue() {
|
||||
return Flux.just(1).onErrorContinue(IllegalArgumentException.class, (e, v) -> {});
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorMap() {
|
||||
return Mono.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorMap() {
|
||||
return Flux.just(1).onErrorMap(IllegalArgumentException.class, e -> e);
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorResume() {
|
||||
return Mono.just(1).onErrorResume(IllegalArgumentException.class, e -> Mono.just(2));
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorResume() {
|
||||
return Flux.just(1).onErrorResume(IllegalArgumentException.class, e -> Flux.just(2));
|
||||
}
|
||||
|
||||
Mono<Integer> testMonoOnErrorReturn() {
|
||||
return Mono.just(1).onErrorReturn(IllegalArgumentException.class, 2);
|
||||
}
|
||||
|
||||
Flux<Integer> testFluxOnErrorReturn() {
|
||||
return Flux.just(1).onErrorReturn(IllegalArgumentException.class, 2);
|
||||
}
|
||||
|
||||
ImmutableSet<Context> testContextEmpty() {
|
||||
return ImmutableSet.of(Context.empty(), Context.empty());
|
||||
}
|
||||
|
||||
ImmutableSet<PublisherProbe<Void>> testPublisherProbeEmpty() {
|
||||
return ImmutableSet.of(PublisherProbe.empty(), PublisherProbe.empty());
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
ZoneId.from(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDate> testLocalDateOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDate(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDate(),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDate(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalDate());
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalDateTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalDateTime(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalDateTime());
|
||||
}
|
||||
|
||||
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
Instant.EPOCH.atZone(ZoneId.of("Europe/Amsterdam")).toLocalTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toLocalTime(),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")).toLocalTime(),
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN).toLocalTime(),
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX).toLocalTime());
|
||||
}
|
||||
|
||||
OffsetDateTime testOffsetDateTimeOfInstant() {
|
||||
return Instant.EPOCH.atZone(ZoneOffset.UTC).toOffsetDateTime();
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")).toOffsetTime(),
|
||||
Instant.EPOCH.atOffset(ZoneOffset.UTC).toOffsetTime());
|
||||
}
|
||||
|
||||
ZonedDateTime testInstantAtZone() {
|
||||
return ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
Clock testUtcClock() {
|
||||
return Clock.system(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
@@ -35,10 +36,48 @@ final class TimeRulesTest implements RefasterRuleCollectionTestCase {
|
||||
ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDate> testLocalDateOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
|
||||
LocalDate.ofInstant(Instant.EPOCH, ZoneOffset.MIN));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalDateTime> testLocalDateTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalDateTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")));
|
||||
}
|
||||
|
||||
ImmutableSet<LocalTime> testLocalTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Berlin")),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MIN),
|
||||
LocalTime.ofInstant(Instant.EPOCH, ZoneOffset.MAX));
|
||||
}
|
||||
|
||||
OffsetDateTime testOffsetDateTimeOfInstant() {
|
||||
return OffsetDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
OffsetDateTime testInstantAtOffset() {
|
||||
return Instant.EPOCH.atOffset(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
ImmutableSet<OffsetTime> testOffsetTimeOfInstant() {
|
||||
return ImmutableSet.of(
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneId.of("Europe/Amsterdam")),
|
||||
OffsetTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
ZonedDateTime testInstantAtZone() {
|
||||
return Instant.EPOCH.atZone(ZoneOffset.UTC);
|
||||
}
|
||||
|
||||
Clock testUtcClock() {
|
||||
return Clock.systemUTC();
|
||||
}
|
||||
|
||||
52
pom.xml
52
pom.xml
@@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Picnic :: Error Prone Support</name>
|
||||
@@ -148,15 +148,15 @@
|
||||
<version.auto-service>1.0.1</version.auto-service>
|
||||
<version.auto-value>1.10</version.auto-value>
|
||||
<version.error-prone>${version.error-prone-orig}</version.error-prone>
|
||||
<version.error-prone-fork>v${version.error-prone-orig}-picnic-3</version.error-prone-fork>
|
||||
<version.error-prone-orig>2.15.0</version.error-prone-orig>
|
||||
<version.error-prone-slf4j>0.1.15</version.error-prone-slf4j>
|
||||
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
|
||||
<version.error-prone-orig>2.16</version.error-prone-orig>
|
||||
<version.error-prone-slf4j>0.1.16</version.error-prone-slf4j>
|
||||
<version.guava-beta-checker>1.0</version.guava-beta-checker>
|
||||
<version.jdk>11</version.jdk>
|
||||
<version.maven>3.8.6</version.maven>
|
||||
<version.mockito>4.8.0</version.mockito>
|
||||
<version.mockito>4.8.1</version.mockito>
|
||||
<version.nopen-checker>1.0.1</version.nopen-checker>
|
||||
<version.nullaway>0.10.2</version.nullaway>
|
||||
<version.nullaway>0.10.4</version.nullaway>
|
||||
<!-- XXX: Two other dependencies are potentially of interest:
|
||||
`com.palantir.assertj-automation:assertj-refaster-rules` and
|
||||
`com.palantir.baseline:baseline-refaster-rules` contain Refaster rules
|
||||
@@ -219,7 +219,7 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>2.13.4</version>
|
||||
<version>2.13.4.20221013</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -243,11 +243,6 @@
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<version>${version.auto-value}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.googlejavaformat</groupId>
|
||||
<artifactId>google-java-format</artifactId>
|
||||
@@ -282,7 +277,7 @@
|
||||
<dependency>
|
||||
<groupId>com.newrelic.agent.java</groupId>
|
||||
<artifactId>newrelic-api</artifactId>
|
||||
<version>7.10.0</version>
|
||||
<version>7.11.0</version>
|
||||
</dependency>
|
||||
<!-- Specified as a workaround for
|
||||
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
|
||||
@@ -308,7 +303,7 @@
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-bom</artifactId>
|
||||
<version>2020.0.23</version>
|
||||
<version>2020.0.24</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
@@ -320,12 +315,12 @@
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.7</version>
|
||||
<version>1.6.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>2.2.3</version>
|
||||
<version>2.2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
@@ -370,7 +365,7 @@
|
||||
<dependency>
|
||||
<groupId>org.checkerframework</groupId>
|
||||
<artifactId>checker-qual</artifactId>
|
||||
<version>3.26.0</version>
|
||||
<version>3.27.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@@ -416,7 +411,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-test</artifactId>
|
||||
<version>2.7.4</version>
|
||||
<version>2.7.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testng</groupId>
|
||||
@@ -628,11 +623,15 @@
|
||||
</property>
|
||||
<property name="illegalClasses" value="com.mongodb.lang.Nullable">
|
||||
<!-- Instead, please use
|
||||
`javax.annotation.Nullable`. -->
|
||||
`org.jspecify.nullness.Nullable`. -->
|
||||
</property>
|
||||
<property name="illegalClasses" value="io.micrometer.core.lang.Nullable">
|
||||
<!-- Instead, please use
|
||||
`javax.annotation.Nullable`. -->
|
||||
`org.jspecify.nullness.Nullable`. -->
|
||||
</property>
|
||||
<property name="illegalClasses" value="javax.annotation.Nullable">
|
||||
<!-- Instead, please use
|
||||
`org.jspecify.nullness.Nullable`. -->
|
||||
</property>
|
||||
<property name="illegalClasses" value="javax.annotation.concurrent.Immutable">
|
||||
<!-- Instead, please use
|
||||
@@ -648,7 +647,7 @@
|
||||
</property>
|
||||
<property name="illegalClasses" value="org.springframework.lang.Nullable">
|
||||
<!-- Instead, please use
|
||||
`javax.annotation.Nullable`. -->
|
||||
`org.jspecify.nullness.Nullable`. -->
|
||||
</property>
|
||||
<property name="illegalPkgs" value="com.amazonaws.annotation" />
|
||||
<property name="illegalPkgs" value="com.beust.jcommander.internal" />
|
||||
@@ -784,7 +783,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.3.4</version>
|
||||
<version>10.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.spring.nohttp</groupId>
|
||||
@@ -1217,12 +1216,15 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>tidy-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<version>1.2.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>versions-maven-plugin</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<version>2.13.0</version>
|
||||
<configuration>
|
||||
<updateBuildOutputTimestampPolicy>never</updateBuildOutputTimestampPolicy>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.gaul</groupId>
|
||||
@@ -1263,7 +1265,7 @@
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.9.8</version>
|
||||
<version>1.9.9</version>
|
||||
<configuration>
|
||||
<excludedClasses>
|
||||
<excludedClass>*.AutoValue_*</excludedClass>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
@@ -36,14 +36,14 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -12,6 +12,9 @@ import com.sun.tools.javac.api.BasicJavacTask;
|
||||
*/
|
||||
@AutoService(Plugin.class)
|
||||
public final class RefasterRuleCompiler implements Plugin {
|
||||
/** Instantiates a new {@link RefasterRuleCompiler} instance. */
|
||||
public RefasterRuleCompiler() {}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getSimpleName();
|
||||
|
||||
@@ -28,10 +28,10 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.StandardLocation;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.AnnotatedCompositeCodeTransformer;
|
||||
|
||||
/**
|
||||
@@ -71,10 +71,10 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
|
||||
|
||||
private ImmutableMap<ClassTree, CodeTransformer> compileRefasterRules(ClassTree tree) {
|
||||
ImmutableMap.Builder<ClassTree, CodeTransformer> rules = ImmutableMap.builder();
|
||||
new TreeScanner<Void, ImmutableClassToInstanceMap<Annotation>>() {
|
||||
@Nullable
|
||||
new TreeScanner<@Nullable Void, ImmutableClassToInstanceMap<Annotation>>() {
|
||||
@Override
|
||||
public Void visitClass(ClassTree node, ImmutableClassToInstanceMap<Annotation> annotations) {
|
||||
public @Nullable Void visitClass(
|
||||
ClassTree node, ImmutableClassToInstanceMap<Annotation> annotations) {
|
||||
ClassSymbol symbol = ASTHelpers.getSymbol(node);
|
||||
|
||||
ImmutableList<CodeTransformer> transformers =
|
||||
@@ -105,7 +105,7 @@ final class RefasterRuleCompilerTaskListener implements TaskListener {
|
||||
|
||||
private static boolean containsRefasterRules(ClassTree tree) {
|
||||
return Boolean.TRUE.equals(
|
||||
new TreeScanner<Boolean, Void>() {
|
||||
new TreeScanner<Boolean, @Nullable Void>() {
|
||||
@Override
|
||||
public Boolean visitAnnotation(AnnotationTree node, @Nullable Void unused) {
|
||||
Symbol sym = ASTHelpers.getSymbol(node);
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
* files on the classpath.
|
||||
*/
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster.plugin;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-runner</artifactId>
|
||||
@@ -51,11 +51,6 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
@@ -66,6 +61,11 @@
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
@@ -61,9 +61,10 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@SuppressWarnings("serial" /* Concrete instance will be `Serializable`. */)
|
||||
private final CodeTransformer codeTransformer;
|
||||
|
||||
/** Instantiates the default {@link Refaster}. */
|
||||
/** Instantiates a default {@link Refaster} instance. */
|
||||
public Refaster() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Exposes Refaster rules found on the classpath through a regular Error Prone check. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster.runner;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-support</artifactId>
|
||||
@@ -51,21 +51,20 @@
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
||||
@@ -42,6 +42,8 @@ public abstract class AnnotatedCompositeCodeTransformer implements CodeTransform
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Splitter CLASS_NAME_SPLITTER = Splitter.on('.').limit(2);
|
||||
|
||||
AnnotatedCompositeCodeTransformer() {}
|
||||
|
||||
abstract String packageName();
|
||||
|
||||
abstract ImmutableList<CodeTransformer> transformers();
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
* non-patch mode.
|
||||
*/
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster.annotation;
|
||||
|
||||
@@ -11,6 +11,9 @@ public final class IsArray implements Matcher<ExpressionTree> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> DELEGATE = isArrayType();
|
||||
|
||||
/** Instantiates a new {@link IsArray} instance. */
|
||||
public IsArray() {}
|
||||
|
||||
@Override
|
||||
public boolean matches(ExpressionTree tree, VisitorState state) {
|
||||
return DELEGATE.matches(tree, state);
|
||||
|
||||
@@ -14,6 +14,9 @@ public final class IsCharacter implements Matcher<ExpressionTree> {
|
||||
private static final Matcher<ExpressionTree> DELEGATE =
|
||||
anyOf(isSameType(CHAR_TYPE), isSameType(Character.class));
|
||||
|
||||
/** Instantiates a new {@link IsCharacter} instance. */
|
||||
public IsCharacter() {}
|
||||
|
||||
@Override
|
||||
public boolean matches(ExpressionTree tree, VisitorState state) {
|
||||
return DELEGATE.matches(tree, state);
|
||||
|
||||
@@ -19,6 +19,9 @@ import java.util.Collection;
|
||||
public final class ThrowsCheckedException implements Matcher<ExpressionTree> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link ThrowsCheckedException} instance. */
|
||||
public ThrowsCheckedException() {}
|
||||
|
||||
@Override
|
||||
public boolean matches(ExpressionTree tree, VisitorState state) {
|
||||
return containsCheckedException(getThrownTypes(tree, state), state);
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
* com.google.errorprone.refaster.annotation.NotMatches @NotMatches} annotations.
|
||||
*/
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster.matchers;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Assorted classes that aid the compilation or evaluation of Refaster rules. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster;
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
|
||||
/**
|
||||
* An abstract {@link BugChecker} that reports a match for each expression matched by the given
|
||||
@@ -31,10 +31,9 @@ abstract class AbstractMatcherTestChecker extends BugChecker implements Compilat
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree compilationUnit, VisitorState state) {
|
||||
new TreeScanner<Void, Void>() {
|
||||
@Nullable
|
||||
new TreeScanner<@Nullable Void, @Nullable Void>() {
|
||||
@Override
|
||||
public Void scan(Tree tree, @Nullable Void unused) {
|
||||
public @Nullable Void scan(Tree tree, @Nullable Void unused) {
|
||||
if (tree instanceof ExpressionTree && delegate.matches((ExpressionTree) tree, state)) {
|
||||
state.reportMatch(
|
||||
Description.builder(tree, canonicalName(), null, defaultSeverity(), message())
|
||||
@@ -44,9 +43,8 @@ abstract class AbstractMatcherTestChecker extends BugChecker implements Compilat
|
||||
return super.scan(tree, unused);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitImport(ImportTree node, @Nullable Void unused) {
|
||||
public @Nullable Void visitImport(ImportTree node, @Nullable Void unused) {
|
||||
/*
|
||||
* We're not interested in matching import statements. While components of these
|
||||
* can be `ExpressionTree`s, they will never be matched by Refaster.
|
||||
@@ -54,9 +52,8 @@ abstract class AbstractMatcherTestChecker extends BugChecker implements Compilat
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitMethod(MethodTree node, @Nullable Void unused) {
|
||||
public @Nullable Void visitMethod(MethodTree node, @Nullable Void unused) {
|
||||
/*
|
||||
* We're not interested in matching e.g. parameter and return type declarations. While these
|
||||
* can be `ExpressionTree`s, they will never be matched by Refaster.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>tech.picnic.error-prone-support</groupId>
|
||||
<artifactId>error-prone-support</artifactId>
|
||||
<version>0.3.1-SNAPSHOT</version>
|
||||
<version>0.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-test-support</artifactId>
|
||||
@@ -45,15 +45,15 @@
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
|
||||
@@ -44,7 +44,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
import tech.picnic.errorprone.refaster.runner.CodeTransformers;
|
||||
import tech.picnic.errorprone.refaster.runner.Refaster;
|
||||
|
||||
@@ -239,16 +239,15 @@ public final class RefasterRuleCollection extends BugChecker implements Compilat
|
||||
return value.substring(index + 1);
|
||||
}
|
||||
|
||||
private class UnexpectedMatchReporter extends TreeScanner<Void, VisitorState> {
|
||||
private class UnexpectedMatchReporter extends TreeScanner<@Nullable Void, VisitorState> {
|
||||
private final ImmutableRangeMap<Integer, String> indexedMatches;
|
||||
|
||||
UnexpectedMatchReporter(ImmutableRangeMap<Integer, String> indexedMatches) {
|
||||
this.indexedMatches = indexedMatches;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Void visitMethod(MethodTree tree, VisitorState state) {
|
||||
public @Nullable Void visitMethod(MethodTree tree, VisitorState state) {
|
||||
if (!ASTHelpers.isGeneratedConstructor(tree)) {
|
||||
getRuleUnderTest(tree, state)
|
||||
.ifPresent(ruleUnderTest -> reportUnexpectedMatches(tree, ruleUnderTest, state));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Opinionated utilities for the testing of Refaster rules. */
|
||||
@com.google.errorprone.annotations.CheckReturnValue
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
@org.jspecify.nullness.NullMarked
|
||||
package tech.picnic.errorprone.refaster.test;
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.refaster.annotation.Placeholder;
|
||||
import com.google.errorprone.refaster.annotation.UseImportPolicy;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import org.jspecify.nullness.Nullable;
|
||||
|
||||
/** Refaster rule collection to validate that having no violations works as expected. */
|
||||
final class ValidRules {
|
||||
|
||||
Reference in New Issue
Block a user