Compare commits

...

60 Commits

Author SHA1 Message Date
Stephan Schroevers
f9910160c7 WIP: Generate documentation
(Manual run with `mvn exec:java@generate-bugpattern-docs -pl refaster-runner`.)

TODO: Actually hook this up in a website
2023-01-07 18:31:57 +01:00
Benedek Halasi
feb9abfa91 Introduce MapGetOrDefault Refaster rule (#439)
Fixes #431.
2023-01-06 14:57:12 +01:00
Stephan Schroevers
560f52bad0 [maven-release-plugin] prepare for next development iteration 2023-01-06 11:29:12 +01:00
Stephan Schroevers
2356c61314 [maven-release-plugin] prepare release v0.7.0 2023-01-06 11:29:09 +01:00
Stephan Schroevers
9a9ef3c59d Have apply-error-prone-suggestions.sh download JitPack-hosted artifacts (#441)
While there, tweak the usage message of both `apply-error-prone-suggestions.sh`
and `run-mutation-tests.sh`.
2023-01-06 10:38:54 +01:00
Rick Ossendrijver
e9a1d54035 Add @OnlineDocumentation to TestNGToAssertJRules (#447) 2023-01-06 10:28:21 +01:00
Picnic-Bot
e9733f7426 Upgrade AssertJ Core 3.23.1 -> 3.24.0 (#448)
While there, use the new BOM.

See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.23.1...assertj-build-3.24.0
2023-01-06 09:12:29 +01:00
Picnic-Bot
534ebb62a1 Upgrade Checker Framework Annotations 3.28.0 -> 3.29.0 (#449)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.29.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.28.0...checker-framework-3.29.0
2023-01-06 08:40:54 +01:00
Rick Ossendrijver
1ed1e6cd03 Update year to 2023 in footer_custom.html and LICENSE.md (#446) 2023-01-05 14:47:26 +01:00
Picnic-Bot
85e3db6f0a Upgrade pitest-maven-plugin 1.10.3 -> 1.10.4 (#445)
See:
- https://github.com/hcoles/pitest/releases/tag/1.10.4
- https://github.com/hcoles/pitest/compare/1.10.3...1.10.4
2023-01-05 09:58:06 +01:00
Picnic-Bot
6f4db8fc4d Upgrade NullAway 0.10.6 -> 0.10.7 (#444)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.6...v0.10.7
2023-01-05 08:32:46 +01:00
chamil-prabodha
9d08e8fd4d Have RequestParamType ignore parameter types with custom deserialization support (#426)
While there, introduce and use a new `Flags` utility class; various checks'
list flags now better support empty lists.
2023-01-04 11:13:44 +01:00
Picnic-Bot
9992ff49ce Upgrade pitest-junit5-plugin 1.1.0 -> 1.1.1 (#440)
See https://github.com/pitest/pitest-junit5-plugin/compare/1.1.0...1.1.1
2023-01-04 10:21:44 +01:00
jarmilakaiser
190b47870b Show original Cody in README and on website home page (#438)
This reverts commit 0153c1495f.
2023-01-04 09:27:21 +01:00
Stephan Schroevers
becfcb5374 Upgrade Error Prone 2.16 -> 2.17.0 (#432)
See:
- https://github.com/google/error-prone/releases/tag/v2.17.0
- https://github.com/google/error-prone/compare/v2.16...v2.17.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.16-picnic-2...v2.17.0-picnic-1
2023-01-03 13:47:21 +01:00
Benedek Halasi
d45682143d Introduce/extend RequireNonNullElse{,Get} Refaster rules (#425)
Fixes #364.
2023-01-02 10:25:10 +01:00
Picnic-Bot
4237732c5b Upgrade errorprone-slf4j 0.1.16 -> 0.1.17 (#433)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.17
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.16...v0.1.17
2023-01-02 10:04:51 +01:00
Picnic-Bot
d7c86c4854 Upgrade Checkstyle 10.5.0 -> 10.6.0 (#435)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.6.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.5.0...checkstyle-10.6.0
2023-01-02 09:33:21 +01:00
Christos Giallouros
e6e50717d3 Introduce JUnitToAssertJRules Refaster rule collection (#417) 2023-01-02 08:51:46 +01:00
Picnic-Bot
834f9ae49b Upgrade NullAway 0.10.5 -> 0.10.6 (#429)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.5...v0.10.6
2022-12-30 12:22:12 +01:00
Pieter Dirk Soels
601fcf2648 Update website styling and add Google site verification (#408) 2022-12-30 08:46:27 +01:00
Picnic-Bot
27c6c48e68 Upgrade Mockito 4.10.0 -> 4.11.0 (#427)
See:
- https://github.com/mockito/mockito/releases/tag/v4.11.0
- https://github.com/mockito/mockito/compare/v4.10.0...v4.11.0
2022-12-29 08:02:16 +01:00
Picnic-Bot
b22078657a Upgrade AspectJ 1.9.9.1 -> 1.9.19 (#422)
See:
- https://github.com/eclipse/org.aspectj/releases/tag/V1_9_19
- https://github.com/eclipse/org.aspectj/compare/V1_9_9_1...V1_9_19
2022-12-27 08:51:16 +01:00
Picnic-Bot
165a003f6a Upgrade Spring Boot 2.7.6 -> 2.7.7 (#423)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.7
- https://github.com/spring-projects/spring-boot/compare/v2.7.6...v2.7.7
2022-12-27 08:34:41 +01:00
Picnic-Bot
ecb8820d80 Upgrade versions-maven-plugin 2.14.1 -> 2.14.2 (#424)
See:
- https://github.com/mojohaus/versions/releases/tag/2.14.2
- https://github.com/mojohaus/versions-maven-plugin/compare/2.14.1...2.14.2
2022-12-27 06:38:04 +01:00
Stephan Schroevers
6313bd56d8 Improve JUnitMethodDeclaration check (#406)
Implemented changes:
- Ignore method overrides even if not annotated with `@Override`.
- Don't rename methods to `true`, `false` or `null`.
- Don't rename methods to a name declared in a super type. This
  prevents e.g. renaming `testToString` to `toString`.
2022-12-22 08:34:11 +01:00
Stephan Schroevers
5665470fe4 Improve IdentityConversion check (#407)
If the result of an explicit boxing operation is immediately
dereferenced, then the explicit conversion operation is not redundant.
2022-12-21 09:44:30 +01:00
EvgheniiShipilov
d0a89da24d Have IdentityConversion flag com.google.errorprone.matchers.Matchers#{allOf,anyOf} (#420)
While there, sort and rename some (test) code.

Fixes #340.
2022-12-20 11:14:46 +01:00
Picnic-Bot
7c40fdc033 Upgrade Arcmutate 1.0.1 -> 1.0.2 (#418) 2022-12-19 21:02:39 +01:00
Picnic-Bot
8724701baf Upgrade Immutables Annotations 2.9.2 -> 2.9.3 (#413)
See:
- https://github.com/immutables/immutables/releases/tag/2.9.3
- https://github.com/immutables/immutables/compare/2.9.2...2.9.3
2022-12-19 14:12:40 +01:00
Picnic-Bot
e9ae238c2b Upgrade Mockito 4.9.0 -> 4.10.0 (#416)
See:
- https://github.com/mockito/mockito/releases/tag/v4.10.0
- https://github.com/mockito/mockito/compare/v4.9.0...v4.10.0
2022-12-19 13:52:49 +01:00
Picnic-Bot
ff2ed6f82c Upgrade JSpecify 0.2.0 -> 0.3.0 (#415)
See:
- https://github.com/jspecify/jspecify/releases/tag/v0.3.0-alpha-1
- https://github.com/jspecify/jspecify/releases/tag/v0.3.0-alpha-2
- https://github.com/jspecify/jspecify/releases/tag/v0.3.0-alpha-3
- https://github.com/jspecify/jspecify/releases/tag/v0.3.0
- https://github.com/jspecify/jspecify/compare/v0.2.0...v0.3.0
2022-12-19 13:14:50 +01:00
Picnic-Bot
17aeeb9ea9 Upgrade versions-maven-plugin 2.13.0 -> 2.14.1 (#414)
See:
- https://github.com/mojohaus/versions/releases/tag/2.14.0
- https://github.com/mojohaus/versions/releases/tag/2.14.1
- https://github.com/mojohaus/versions-maven-plugin/compare/2.13.0...2.14.1
2022-12-19 12:56:03 +01:00
Stephan Schroevers
fd2946a9c8 Disable failing JDK 20-ea build for now (#419)
The build fails due to
openjdk/jdk20@2cb64a7557; the upcoming
Error Prone release includes a workaround for this:
google/error-prone@df033f03cb.
2022-12-19 09:37:03 +01:00
Guillaume Toison
870d16a0b6 Prevent NestedOptionals from throwing an NPE (#412)
Previously, a `NullPointerException` was thrown if during compilation the
`java.util.Optional` class was not loaded at all.
2022-12-16 09:40:05 +01:00
Picnic-Bot
96114235c5 Upgrade Project Reactor 2022.0.0 -> 2022.0.1 (#411)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.1
- https://github.com/reactor/reactor/compare/2022.0.0...2022.0.1
2022-12-14 07:18:40 +01:00
Picnic-Bot
bfbf748d47 Upgrade SLF4J API 2.0.5 -> 2.0.6 (#409)
See:
- https://www.slf4j.org/news.html
- https://github.com/qos-ch/slf4j/compare/v_2.0.5...v_2.0.6
2022-12-13 17:11:06 +01:00
Stephan Schroevers
8d0f1d78e6 Upgrade Error Prone fork v2.16-picnic-1 -> v2.16-picnic-2 (#410)
See:
- https://github.com/PicnicSupermarket/error-prone/releases/tag/v2.16-picnic-2
- https://github.com/PicnicSupermarket/error-prone/compare/v2.16-picnic-1...v2.16-picnic-2
2022-12-13 12:51:50 +01:00
Stephan Schroevers
ec00a5522f [maven-release-plugin] prepare for next development iteration 2022-12-12 09:57:05 +01:00
Stephan Schroevers
465b16c471 [maven-release-plugin] prepare release v0.6.0 2022-12-12 09:57:03 +01:00
Paco van Beckhoven
2cbd48ec47 Introduce MonoIdentity and MonoThen Refaster rules (#405)
The `MonoIdentity` rule is a generalization of the existing
`MonoSwitchIfEmptyOfEmptyPublisher` rule.
2022-12-12 08:52:56 +01:00
jarmilakaiser
0153c1495f Show Christmas Cody in README and on website home page (#404) 2022-12-09 16:27:51 +01:00
Rick Ossendrijver
81450285be Fix suggestions emitted by the StringCaseLocaleUsage check (#400)
The suggested `Locale` arguments are now always located in the correct place.
2022-12-09 14:35:35 +01:00
Bastien Diederichs
096acfb14f Improve IsInstanceLambdaUsage check (#401)
Fixes #399.
2022-12-09 13:27:46 +01:00
Shang Xiang
17bcdb6faa Introduce Flux and Stream Refaster rules to suggest filtering before sorting (#393)
Fixes #386.
2022-12-09 13:07:31 +01:00
Rick Ossendrijver
3ee527fda2 Drop indentation in feature request issue template (#403)
While there, add "Improve performance" as a rewrite reason.
2022-12-09 08:55:12 +01:00
Stephan Schroevers
b1c815770b Prevent ReverseOrder Refaster rule from introducing a static import (#397)
This is a workaround for the issue resolved by google/error-prone#3584.

After application of this Refaster rule, any static imports of
`java.util.Collections.reverseOrder` are obsolete. These can be removed by
running Google Java Format or Error Prone's `RemoveUnusedImports` check.

Where possible, subsequent application of the `StaticImport` check will
statically import `java.util.Comparator.reverseOrder`.
2022-12-08 09:06:19 +01:00
Vincent Koeman
bc1f204877 Prefer BigDecimal.valueOf(double) over new BigDecimal(double) (#394)
See https://rules.sonarsource.com/java/RSPEC-2111
2022-12-07 18:58:54 +01:00
Picnic-Bot
cf995ece2b Upgrade actions/setup-java v3.6.0 -> v3.8.0 (#395)
See:
- https://github.com/actions/setup-java/releases/tag/v3.8.0
- https://github.com/actions/setup-java/compare/v3.6.0...v3.8.0
2022-12-07 11:48:55 +01:00
Stephan Schroevers
d427e298e2 Introduce additional Refaster rules to ComparatorRules (#388) 2022-12-07 11:38:25 +01:00
Picnic-Bot
ae327d8d64 Upgrade pitest-maven-plugin 1.9.11 -> 1.10.3 (#378)
See:
- https://github.com/hcoles/pitest/releases/tag/1.10.0
- https://github.com/hcoles/pitest/releases/tag/1.10.1
- https://github.com/hcoles/pitest/releases/tag/1.10.2
- https://github.com/hcoles/pitest/releases/tag/1.10.3
- https://github.com/hcoles/pitest/compare/1.9.11...1.10.3
2022-12-06 11:58:47 +01:00
Gonzalo Amestoy
a6f794de3d Introduce CollectionForEach Refaster rule (#390)
Fixes #387.
2022-12-06 09:28:24 +01:00
Picnic-Bot
1794d36053 Upgrade Pitest Git plugins 1.0.2 -> 1.0.3 (#391) 2022-12-06 09:04:37 +01:00
Phil Werli
ee62af4a86 Introduce MonoFromOptionalSwitchIfEmpty and OptionalMapMonoJust Refaster rules (#384) 2022-12-06 08:26:34 +01:00
Phil Werli
1afce12b52 Introduce Mono{Empty,Just,JustOrEmpty} Refaster rules (#385) 2022-12-06 08:15:39 +01:00
Rick Ossendrijver
f585306a1f Downgrade actions/setup-java v3.7.0 -> v3.6.0 (#392)
This reverts commit 5afa7e1878. Tag v3.7.0 was
deleted; see actions/setup-java#422 for details.
2022-12-06 08:06:58 +01:00
Luke Prananta
4f9aba83ec Introduce StringCaseLocaleUsage check (#376) 2022-12-05 13:49:20 +01:00
Stephan Schroevers
066591c379 Improve mutation testing setup (#383)
Summary of changes:
- Enable Pitest's built-in `STRONGER` mutator group.
- Enable Arcmutate's `EXTENDED` mutator group.
- Enable Arcmutate's JUnit 5 Accelerator Plugin.
- Modify `MoreTypesTest` such that it is impacted by mutations of the
  `MoreTypes` class.

See:
- https://pitest.org/quickstart/mutators/
- https://docs.arcmutate.com/docs/extended-operators.html
- https://docs.arcmutate.com/docs/accelerator.html
2022-12-05 09:11:51 +01:00
Picnic-Bot
789f8c86f2 Upgrade Pitest Git plugins 1.0.1 -> 1.0.2 (#380) 2022-12-05 09:01:20 +01:00
Picnic-Bot
0ccebcc9c4 Upgrade Checker Framework Annotations 3.27.0 -> 3.28.0 (#382)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.28.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.28.0
2022-12-04 19:57:42 +01:00
98 changed files with 2450 additions and 290 deletions

View File

@@ -17,23 +17,24 @@ you'd like to be solved through Error Prone Support. -->
- [ ] Support a stylistic preference.
- [ ] Avoid a common gotcha, or potential problem.
- [ ] Improve performance.
<!--
Here, provide a clear and concise description of the desired change.
If possible, provide a simple and minimal example using the following format:
I would like to rewrite the following code:
```java
// XXX: Write the code to match here.
```
to:
```java
// XXX: Write the desired code here.
```
-->
I would like to rewrite the following code:
```java
// XXX: Write the code to match here.
```
to:
```java
// XXX: Write the desired code here.
```
### Considerations
<!--

View File

@@ -37,7 +37,7 @@ jobs:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.7.0
uses: actions/setup-java@v3.8.0
with:
java-version: ${{ matrix.jdk }}
distribution: ${{ matrix.distribution }}

View File

@@ -16,7 +16,7 @@ jobs:
with:
fetch-depth: 2
- name: Set up JDK
uses: actions/setup-java@v3.7.0
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin

View File

@@ -20,7 +20,7 @@ jobs:
- name: Check out code
uses: actions/checkout@v3.1.0
- name: Set up JDK
uses: actions/setup-java@v3.7.0
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.4
distribution: temurin

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2022 Picnic Technologies BV
Copyright (c) 2017-2023 Picnic Technologies BV
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -9,13 +9,14 @@
set -e -u -o pipefail
if [ "${#}" -gt 1 ]; then
echo "Usage: ./$(basename "${0}") [PatchChecks]"
echo "Usage: ${0} [PatchChecks]"
exit 1
fi
patchChecks=${1:-}
mvn clean test-compile fmt:format \
-s "$(dirname "${0}")/settings.xml" \
-T 1.0C \
-Perror-prone \
-Perror-prone-fork \

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.5.1-SNAPSHOT</version>
<version>0.7.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -146,7 +146,7 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
@@ -242,5 +242,11 @@
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -32,7 +32,7 @@ import com.sun.source.util.SimpleTreeVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**

View File

@@ -23,6 +23,7 @@ import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
@@ -32,7 +33,7 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags redundant identity conversions. */
// XXX: Consider detecting cases where a flagged expression is passed to a method, and where removal
// of the identify conversion would cause a different method overload to be selected. Depending on
// of the identity conversion would cause a different method overload to be selected. Depending on
// the target method such a modification may change the code's semantics or performance.
@AutoService(BugChecker.class)
@BugPattern(
@@ -45,6 +46,13 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
@@ -60,12 +68,8 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
.onClass("com.google.errorprone.matchers.Matchers")
.namedAnyOf("allOf", "anyOf"),
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
staticMethod()
.onClass("reactor.core.publisher.Flux")
@@ -95,6 +99,15 @@ public final class IdentityConversion extends BugChecker implements MethodInvoca
return Description.NO_MATCH;
}
if (sourceType.isPrimitive()
&& state.getPath().getParentPath().getLeaf() instanceof MemberSelectTree) {
/*
* The result of the conversion method is dereferenced, while the source type is a primitive:
* dropping the conversion would yield uncompilable code.
*/
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
"This method invocation appears redundant; remove it or suppress this warning and "

View File

@@ -6,15 +6,18 @@ 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.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
@@ -39,15 +42,19 @@ public final class IsInstanceLambdaUsage extends BugChecker implements LambdaExp
@Override
public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) {
if (tree.getKind() != Kind.LAMBDA_EXPRESSION || tree.getBody().getKind() != Kind.INSTANCE_OF) {
if (tree.getParameters().size() != 1 || tree.getBody().getKind() != Kind.INSTANCE_OF) {
return Description.NO_MATCH;
}
VariableTree param = Iterables.getOnlyElement(tree.getParameters());
InstanceOfTree instanceOf = (InstanceOfTree) tree.getBody();
if (!ASTHelpers.getSymbol(param).equals(ASTHelpers.getSymbol(instanceOf.getExpression()))) {
return Description.NO_MATCH;
}
return describeMatch(
tree,
SuggestedFix.replace(
tree,
SourceCode.treeToString(((InstanceOfTree) tree.getBody()).getType(), state)
+ ".class::isInstance"));
tree, SourceCode.treeToString(instanceOf.getType(), state) + ".class::isInstance"));
}
}

View File

@@ -3,21 +3,19 @@ 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 com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.not;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -26,14 +24,15 @@ 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.matchers.Matchers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
@@ -53,21 +52,19 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
private static final long serialVersionUID = 1L;
private static final String TEST_PREFIX = "test";
private static final ImmutableSet<Modifier> ILLEGAL_MODIFIERS =
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final Matcher<MethodTree> HAS_UNMODIFIABLE_SIGNATURE =
anyOf(
annotations(AT_LEAST_ONE, isType("java.lang.Override")),
allOf(
Matchers.not(hasModifier(Modifier.FINAL)),
Matchers.not(hasModifier(Modifier.PRIVATE)),
enclosingClass(hasModifier(Modifier.ABSTRACT))));
Sets.immutableEnumSet(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final Matcher<MethodTree> IS_LIKELY_OVERRIDDEN =
allOf(
not(hasModifier(Modifier.FINAL)),
not(hasModifier(Modifier.PRIVATE)),
enclosingClass(hasModifier(Modifier.ABSTRACT)));
/** Instantiates a new {@link JUnitMethodDeclaration} instance. */
public JUnitMethodDeclaration() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (HAS_UNMODIFIABLE_SIGNATURE.matches(tree, state)) {
if (IS_LIKELY_OVERRIDDEN.matches(tree, state) || isOverride(tree, state)) {
return Description.NO_MATCH;
}
@@ -89,10 +86,11 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
private void suggestTestMethodRenameIfApplicable(
MethodTree tree, SuggestedFix.Builder fixBuilder, VisitorState state) {
tryCanonicalizeMethodName(tree)
MethodSymbol symbol = ASTHelpers.getSymbol(tree);
tryCanonicalizeMethodName(symbol)
.ifPresent(
newName ->
findMethodRenameBlocker(newName, state)
findMethodRenameBlocker(symbol, newName, state)
.ifPresentOrElse(
blocker -> reportMethodRenameBlocker(tree, blocker, state),
() -> fixBuilder.merge(SuggestedFixes.renameMethod(tree, newName, state))));
@@ -124,23 +122,31 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
* consideration cannot be referenced directly.)
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
if (MoreASTHelpers.methodExistsInEnclosingClass(methodName, state)) {
private static Optional<String> findMethodRenameBlocker(
MethodSymbol method, String newName, VisitorState state) {
if (isExistingMethodName(method.owner.type, newName, state)) {
return Optional.of(
String.format("a method named `%s` already exists in this class", methodName));
String.format(
"a method named `%s` is already defined in this class or a supertype", newName));
}
if (isSimpleNameStaticallyImported(methodName, state)) {
return Optional.of(String.format("`%s` is already statically imported", methodName));
if (isSimpleNameStaticallyImported(newName, state)) {
return Optional.of(String.format("`%s` is already statically imported", newName));
}
if (isReservedKeyword(methodName)) {
return Optional.of(String.format("`%s` is a reserved keyword", methodName));
if (!isValidIdentifier(newName)) {
return Optional.of(String.format("`%s` is not a valid identifier", newName));
}
return Optional.empty();
}
private static boolean isExistingMethodName(Type clazz, String name, VisitorState state) {
return ASTHelpers.matchingMethods(state.getName(name), method -> true, clazz, state.getTypes())
.findAny()
.isPresent();
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
@@ -154,12 +160,18 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
return Optional.of(ASTHelpers.getSymbol(tree).getQualifiedName().toString())
private static Optional<String> tryCanonicalizeMethodName(Symbol symbol) {
return Optional.of(symbol.getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))
.filter(not(String::isEmpty))
.map(name -> Character.toLowerCase(name.charAt(0)) + name.substring(1))
.filter(name -> !Character.isDigit(name.charAt(0)));
}
private static boolean isOverride(MethodTree tree, VisitorState state) {
return ASTHelpers.streamSuperMethods(ASTHelpers.getSymbol(tree), state.getTypes())
.findAny()
.isPresent();
}
}

View File

@@ -40,8 +40,9 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.Flags;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
@@ -220,7 +221,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
private static ImmutableList<String> excludedAnnotations(ErrorProneFlags flags) {
Set<String> exclusions = new HashSet<>();
flags.getList(EXCLUDED_ANNOTATIONS_FLAG).ifPresent(exclusions::addAll);
exclusions.addAll(Flags.getList(flags, EXCLUDED_ANNOTATIONS_FLAG));
exclusions.addAll(BLACKLISTED_ANNOTATIONS);
return ImmutableList.copyOf(exclusions);
}

View File

@@ -27,7 +27,7 @@ import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.TypeAnnotations.AnnotationType;
import java.util.Comparator;
import java.util.List;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**

View File

@@ -41,8 +41,11 @@ public final class NestedOptionals extends BugChecker implements MethodInvocatio
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
? describeMatch(tree)
: Description.NO_MATCH;
Type type = OPTIONAL_OF_OPTIONAL.get(state);
if (type == null || !state.getTypes().isSubtype(ASTHelpers.getType(tree), type)) {
return Description.NO_MATCH;
}
return describeMatch(tree);
}
}

View File

@@ -49,6 +49,7 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.Flags;
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
@@ -63,9 +64,8 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
public final class RedundantStringConversion extends BugChecker
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String FLAG_PREFIX = "RedundantStringConversion:";
private static final String EXTRA_STRING_CONVERSION_METHODS_FLAG =
FLAG_PREFIX + "ExtraConversionMethods";
"RedundantStringConversion:ExtraConversionMethods";
@SuppressWarnings("UnnecessaryLambda")
private static final Matcher<ExpressionTree> ANY_EXPR = (t, s) -> true;
@@ -374,10 +374,9 @@ public final class RedundantStringConversion extends BugChecker
ErrorProneFlags flags) {
// XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas.
// For this class methods accepting more than one argument are not valid, but still: not nice.
return flags
.getList(EXTRA_STRING_CONVERSION_METHODS_FLAG)
.map(new MethodMatcherFactory()::create)
.map(m -> anyOf(WELL_KNOWN_STRING_CONVERSION_METHODS, m))
.orElse(WELL_KNOWN_STRING_CONVERSION_METHODS);
return anyOf(
WELL_KNOWN_STRING_CONVERSION_METHODS,
new MethodMatcherFactory()
.create(Flags.getList(flags, EXTRA_STRING_CONVERSION_METHODS_FLAG)));
}
}

View File

@@ -1,5 +1,6 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
@@ -9,41 +10,72 @@ import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import tech.picnic.errorprone.bugpatterns.util.Flags;
/** A {@link BugChecker} that flags {@code @RequestParam} parameters with an unsupported type. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
summary =
"By default, `@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
link = BUG_PATTERNS_BASE_URL + "RequestParamType",
linkType = CUSTOM,
severity = ERROR,
tags = LIKELY_ERROR)
public final class RequestParamType extends BugChecker implements VariableTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<VariableTree> HAS_UNSUPPORTED_REQUEST_PARAM =
allOf(
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)));
private static final String SUPPORTED_CUSTOM_TYPES_FLAG = "RequestParamType:SupportedCustomTypes";
/** Instantiates a new {@link RequestParamType} instance. */
public RequestParamType() {}
private final Matcher<VariableTree> hasUnsupportedRequestParamType;
/** Instantiates a default {@link RequestParamType} instance. */
public RequestParamType() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link RequestParamType} instance.
*
* @param flags Any provided command line flags.
*/
public RequestParamType(ErrorProneFlags flags) {
hasUnsupportedRequestParamType = hasUnsupportedRequestParamType(flags);
}
@Override
public Description matchVariable(VariableTree tree, VisitorState state) {
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)
return hasUnsupportedRequestParamType.matches(tree, state)
? describeMatch(tree)
: Description.NO_MATCH;
}
private static Matcher<VariableTree> hasUnsupportedRequestParamType(ErrorProneFlags flags) {
return allOf(
annotations(AT_LEAST_ONE, isType("org.springframework.web.bind.annotation.RequestParam")),
anyOf(isSubtypeOf(ImmutableCollection.class), isSubtypeOf(ImmutableMap.class)),
not(isSubtypeOfAny(Flags.getList(flags, SUPPORTED_CUSTOM_TYPES_FLAG))));
}
private static Matcher<Tree> isSubtypeOfAny(ImmutableList<String> inclusions) {
return anyOf(
inclusions.stream()
.map(inclusion -> isSubtypeOf(Suppliers.typeFromString(inclusion)))
.collect(toImmutableList()));
}
}

View File

@@ -0,0 +1,89 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.sun.tools.javac.parser.Tokens.TokenKind.RPAREN;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ErrorProneTokens;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.util.Position;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags calls to {@link String#toLowerCase()} and {@link
* String#toUpperCase()}, as these methods implicitly rely on the environment's default locale.
*/
// XXX: Also flag `String::toLowerCase` and `String::toUpperCase` method references. For these cases
// the suggested fix should introduce a lambda expression with a parameter of which the name does
// not coincide with the name of an existing variable name. Such functionality should likely be
// introduced in a utility class.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Specify a `Locale` when calling `String#to{Lower,Upper}Case`",
link = BUG_PATTERNS_BASE_URL + "StringCaseLocaleUsage",
linkType = CUSTOM,
severity = WARNING,
tags = FRAGILE_CODE)
public final class StringCaseLocaleUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> DEFAULT_LOCALE_CASE_CONVERSION =
instanceMethod()
.onExactClass(String.class.getName())
.namedAnyOf("toLowerCase", "toUpperCase")
.withNoParameters();
/** Instantiates a new {@link StringCaseLocaleUsage} instance. */
public StringCaseLocaleUsage() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!DEFAULT_LOCALE_CASE_CONVERSION.matches(tree, state)) {
return Description.NO_MATCH;
}
int closingParenPosition = getClosingParenPosition(tree, state);
if (closingParenPosition == Position.NOPOS) {
return describeMatch(tree);
}
return buildDescription(tree)
.addFix(suggestLocale(closingParenPosition, "Locale.ROOT"))
.addFix(suggestLocale(closingParenPosition, "Locale.getDefault()"))
.build();
}
private static Fix suggestLocale(int insertPosition, String locale) {
return SuggestedFix.builder()
.addImport("java.util.Locale")
.replace(insertPosition, insertPosition, locale)
.build();
}
private static int getClosingParenPosition(MethodInvocationTree tree, VisitorState state) {
int startPosition = ASTHelpers.getStartPosition(tree);
if (startPosition == Position.NOPOS) {
return Position.NOPOS;
}
return Streams.findLast(
ErrorProneTokens.getTokens(SourceCode.treeToString(tree, state), state.context).stream()
.filter(t -> t.kind() == RPAREN))
.map(token -> startPosition + token.pos())
.orElse(Position.NOPOS);
}
}

View File

@@ -27,7 +27,7 @@ import com.sun.tools.javac.util.Convert;
import java.util.Formattable;
import java.util.Iterator;
import java.util.List;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**

View File

@@ -1,4 +1,4 @@
/** Picnic Error Prone Contrib checks. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.bugpatterns;

View File

@@ -0,0 +1,25 @@
package tech.picnic.errorprone.bugpatterns.util;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.ErrorProneFlags;
/** Helper methods for working with {@link ErrorProneFlags}. */
public final class Flags {
private Flags() {}
/**
* Returns the list of (comma-separated) arguments passed using the given Error Prone flag.
*
* @param errorProneFlags The full set of flags provided.
* @param name The name of the flag of interest.
* @return A non-{@code null} list of provided arguments; this list is empty if the flag was not
* provided, or if the flag's value is the empty string.
*/
public static ImmutableList<String> getList(ErrorProneFlags errorProneFlags, String name) {
return errorProneFlags
.getList(name)
.map(ImmutableList::copyOf)
.filter(flags -> !flags.equals(ImmutableList.of("")))
.orElseGet(ImmutableList::of);
}
}

View File

@@ -4,7 +4,19 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
/** Utility class that can be used to identify reserved keywords of the Java language. */
// XXX: This class is no longer only about keywords. Consider changing its name and class-level
// documentation.
public final class JavaKeywords {
/**
* Enumeration of boolean and null literals.
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.10.3">JDK 17
* JLS section 3.10.3: Boolean Literals</a>
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.10.8">JDK 17
* JLS section 3.10.8: The Null Literal</a>
*/
private static final ImmutableSet<String> BOOLEAN_AND_NULL_LITERALS =
ImmutableSet.of("true", "false", "null");
/**
* List of all reserved keywords in the Java language.
*
@@ -64,7 +76,6 @@ public final class JavaKeywords {
"void",
"volatile",
"while");
/**
* List of all contextual keywords in the Java language.
*
@@ -89,13 +100,28 @@ public final class JavaKeywords {
"var",
"with",
"yield");
/** List of all keywords in the Java language. */
private static final ImmutableSet<String> ALL_KEYWORDS =
Sets.union(RESERVED_KEYWORDS, CONTEXTUAL_KEYWORDS).immutableCopy();
private JavaKeywords() {}
/**
* Tells whether the given string is a valid identifier in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a valid identifier in the Java language.
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.8">JDK 17 JLS
* section 3.8: Identifiers</a>
*/
public static boolean isValidIdentifier(String str) {
return !str.isEmpty()
&& !isReservedKeyword(str)
&& !BOOLEAN_AND_NULL_LITERALS.contains(str)
&& Character.isJavaIdentifierStart(str.codePointAt(0))
&& str.codePoints().skip(1).allMatch(Character::isUnicodeIdentifierPart);
}
/**
* Tells whether the given string is a reserved keyword in the Java language.
*

View File

@@ -18,7 +18,7 @@ import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
/**
* A collection of JUnit-specific helper methods and {@link Matcher}s.

View File

@@ -1,4 +1,4 @@
/** Auxiliary utilities for use by Error Prone checks. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.bugpatterns.util;

View File

@@ -22,7 +22,6 @@ final class AssertJOptionalRules {
static final class AssertThatOptional<T> {
@BeforeTemplate
@SuppressWarnings("NullAway")
ObjectAssert<T> before(Optional<T> optional) {
return assertThat(optional.orElseThrow());
}

View File

@@ -24,7 +24,7 @@ import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/**
@@ -103,7 +103,8 @@ 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);
}
}

View File

@@ -50,17 +50,18 @@ final class BigDecimalRules {
}
}
/** Prefer {@link BigDecimal#valueOf(long)} over the associated constructor. */
// XXX: Ideally we'd also rewrite `BigDecimal.valueOf("<some-integer-value>")`, but it doesn't
// appear that's currently possible with Error Prone.
static final class BigDecimalFactoryMethod {
/** Prefer {@link BigDecimal#valueOf(double)} over the associated constructor. */
// XXX: Ideally we also rewrite `new BigDecimal("<some-integer-value>")` in cases where the
// specified number can be represented as an `int` or `long`, but that requires a custom
// `BugChecker`.
static final class BigDecimalValueOf {
@BeforeTemplate
BigDecimal before(long value) {
BigDecimal before(double value) {
return new BigDecimal(value);
}
@AfterTemplate
BigDecimal after(long value) {
BigDecimal after(double value) {
return BigDecimal.valueOf(value);
}
}

View File

@@ -17,6 +17,7 @@ import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -404,6 +405,19 @@ final class CollectionRules {
}
}
/** Prefer {@link Collection#forEach(Consumer)} over more contrived alternatives. */
static final class CollectionForEach<T> {
@BeforeTemplate
void before(Collection<T> collection, Consumer<? super T> consumer) {
collection.stream().forEach(consumer);
}
@AfterTemplate
void after(Collection<T> collection, Consumer<? super T> consumer) {
collection.forEach(consumer);
}
}
// XXX: collection.stream().noneMatch(e -> e.equals(other))
// ^ This is !collection.contains(other). Do we already rewrite variations on this?
}

View File

@@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collections;
@@ -24,6 +25,7 @@ import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Comparator}s. */
@@ -37,7 +39,10 @@ final class ComparatorRules {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
T::compareTo,
comparing(Refaster.anyOf(identity(), v -> v)),
Collections.<T>reverseOrder(reverseOrder()),
Comparator.<T>reverseOrder().reversed());
}
@AfterTemplate
@@ -51,11 +56,15 @@ final class ComparatorRules {
static final class ReverseOrder<T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<T> before() {
return Comparator.<T>naturalOrder().reversed();
return Refaster.anyOf(
Collections.reverseOrder(),
Collections.<T>reverseOrder(naturalOrder()),
Comparator.<T>naturalOrder().reversed());
}
// XXX: Add `@UseImportPolicy(STATIC_IMPORT_ALWAYS)` if/when
// https://github.com/google/error-prone/pull/3584 is merged and released.
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return reverseOrder();
}
@@ -189,15 +198,54 @@ final class ComparatorRules {
}
}
/** Prefer {@link Comparable#compareTo(Object)}} over more verbose alternatives. */
static final class CompareTo<T extends Comparable<? super T>> {
@BeforeTemplate
int before(T value1, T value2) {
return Refaster.anyOf(
Comparator.<T>naturalOrder().compare(value1, value2),
Comparator.<T>reverseOrder().compare(value2, value1));
}
@AfterTemplate
int after(T value1, T value2) {
return value1.compareTo(value2);
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the minimum of a known collection
* of values.
*/
static final class MinOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).min(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.min(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#min(Comparable, Comparable)}} over more verbose alternatives. */
static final class MinOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
return Refaster.anyOf(
value1.compareTo(value2) <= 0 ? value1 : value2,
value1.compareTo(value2) > 0 ? value2 : value1,
value2.compareTo(value1) < 0 ? value2 : value1,
value2.compareTo(value1) >= 0 ? value1 : value2,
Comparators.min(value1, value2, naturalOrder()),
Comparators.max(value1, value2, reverseOrder()),
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
}
@AfterTemplate
@@ -212,12 +260,17 @@ final class ComparatorRules {
static final class MinOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
return Refaster.anyOf(
cmp.compare(value1, value2) <= 0 ? value1 : value2,
cmp.compare(value1, value2) > 0 ? value2 : value1,
cmp.compare(value2, value1) < 0 ? value2 : value1,
cmp.compare(value2, value1) >= 0 ? value1 : value2,
Collections.min(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
}
@AfterTemplate
@@ -226,15 +279,39 @@ final class ComparatorRules {
}
}
/**
* Avoid unnecessary creation of a {@link Stream} to determine the maximum of a known collection
* of values.
*/
static final class MaxOfVarargs<T> {
@BeforeTemplate
@SuppressWarnings("StreamOfArray" /* In practice individual values are provided. */)
T before(@Repeated T value, Comparator<T> cmp) {
return Stream.of(Refaster.asVarargs(value)).max(cmp).orElseThrow();
}
@AfterTemplate
T after(@Repeated T value, Comparator<T> cmp) {
return Collections.max(Arrays.asList(value), cmp);
}
}
/** Prefer {@link Comparators#max(Comparable, Comparable)}} over more verbose alternatives. */
static final class MaxOfPairNaturalOrder<T extends Comparable<? super T>> {
@BeforeTemplate
T before(T value1, T value2) {
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)));
return Refaster.anyOf(
value1.compareTo(value2) >= 0 ? value1 : value2,
value1.compareTo(value2) < 0 ? value2 : value1,
value2.compareTo(value1) > 0 ? value2 : value1,
value2.compareTo(value1) <= 0 ? value1 : value2,
Comparators.max(value1, value2, naturalOrder()),
Comparators.min(value1, value2, reverseOrder()),
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2))));
}
@AfterTemplate
@@ -249,12 +326,17 @@ final class ComparatorRules {
static final class MaxOfPairCustomOrder<T> {
@BeforeTemplate
T before(T value1, T value2, Comparator<T> cmp) {
return Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp);
return Refaster.anyOf(
cmp.compare(value1, value2) >= 0 ? value1 : value2,
cmp.compare(value1, value2) < 0 ? value2 : value1,
cmp.compare(value2, value1) > 0 ? value2 : value1,
cmp.compare(value2, value1) <= 0 ? value1 : value2,
Collections.max(
Refaster.anyOf(
Arrays.asList(value1, value2),
ImmutableList.of(value1, value2),
ImmutableSet.of(value1, value2)),
cmp));
}
@AfterTemplate

View File

@@ -141,6 +141,22 @@ final class DoubleStreamRules {
}
}
/**
* Apply {@link DoubleStream#filter(DoublePredicate)} before {@link DoubleStream#sorted()} to
* reduce the number of elements to sort.
*/
static final class DoubleStreamFilterSorted {
@BeforeTemplate
DoubleStream before(DoubleStream stream, DoublePredicate predicate) {
return stream.sorted().filter(predicate);
}
@AfterTemplate
DoubleStream after(DoubleStream stream, DoublePredicate predicate) {
return stream.filter(predicate).sorted();
}
}
/** In order to test whether a stream has any element, simply try to find one. */
static final class DoubleStreamIsEmpty {
@BeforeTemplate

View File

@@ -154,6 +154,22 @@ final class IntStreamRules {
}
}
/**
* Apply {@link IntStream#filter(IntPredicate)} before {@link IntStream#sorted()} to reduce the
* number of elements to sort.
*/
static final class IntStreamFilterSorted {
@BeforeTemplate
IntStream before(IntStream stream, IntPredicate predicate) {
return stream.sorted().filter(predicate);
}
@AfterTemplate
IntStream after(IntStream stream, IntPredicate predicate) {
return stream.filter(predicate).sorted();
}
}
/** In order to test whether a stream has any element, simply try to find one. */
static final class IntStreamIsEmpty {
@BeforeTemplate

View File

@@ -0,0 +1,521 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.function.Supplier;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.function.ThrowingSupplier;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/**
* Refaster rules to replace JUnit assertions with AssertJ equivalents.
*
* <p>Note that, while both libraries throw an {@link AssertionError} in case of an assertion
* failure, the exact subtype used generally differs.
*/
// XXX: Not all `org.assertj.core.api.Assertions` methods have an associated Refaster rule yet;
// expand this class.
// XXX: Introduce a `@Matcher` on `Executable` and `ThrowingSupplier` expressions, such that they
// are only matched if they are also compatible with the `ThrowingCallable` functional interface.
// When implementing such a matcher, note that expressions with a non-void return type such as
// `() -> toString()` match both `ThrowingSupplier` and `ThrowingCallable`, but `() -> "constant"`
// is only compatible with the former.
@OnlineDocumentation
final class JUnitToAssertJRules {
private JUnitToAssertJRules() {}
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Assertions.class,
assertDoesNotThrow(() -> null),
assertInstanceOf(null, null),
assertThrows(null, null),
assertThrowsExactly(null, null),
(Runnable) () -> assertFalse(true),
(Runnable) () -> assertNotNull(null),
(Runnable) () -> assertNotSame(null, null),
(Runnable) () -> assertNull(null),
(Runnable) () -> assertSame(null, null),
(Runnable) () -> assertTrue(true));
}
static final class ThrowNewAssertionError {
@BeforeTemplate
void before() {
Assertions.fail();
}
@AfterTemplate
@DoNotCall
void after() {
throw new AssertionError();
}
}
static final class FailWithMessage<T> {
@BeforeTemplate
T before(String message) {
return Assertions.fail(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(String message) {
return fail(message);
}
}
static final class FailWithMessageAndThrowable<T> {
@BeforeTemplate
T before(String message, Throwable throwable) {
return Assertions.fail(message, throwable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(String message, Throwable throwable) {
return fail(message, throwable);
}
}
static final class FailWithThrowable {
@BeforeTemplate
void before(Throwable throwable) {
Assertions.fail(throwable);
}
@AfterTemplate
@DoNotCall
void after(Throwable throwable) {
throw new AssertionError(throwable);
}
}
static final class AssertThatIsTrue {
@BeforeTemplate
void before(boolean actual) {
assertTrue(actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual) {
assertThat(actual).isTrue();
}
}
static final class AssertThatWithFailMessageStringIsTrue {
@BeforeTemplate
void before(boolean actual, String message) {
assertTrue(actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual, String message) {
assertThat(actual).withFailMessage(message).isTrue();
}
}
static final class AssertThatWithFailMessageSupplierIsTrue {
@BeforeTemplate
void before(boolean actual, Supplier<String> supplier) {
assertTrue(actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isTrue();
}
}
static final class AssertThatIsFalse {
@BeforeTemplate
void before(boolean actual) {
assertFalse(actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual) {
assertThat(actual).isFalse();
}
}
static final class AssertThatWithFailMessageStringIsFalse {
@BeforeTemplate
void before(boolean actual, String message) {
assertFalse(actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual, String message) {
assertThat(actual).withFailMessage(message).isFalse();
}
}
static final class AssertThatWithFailMessageSupplierIsFalse {
@BeforeTemplate
void before(boolean actual, Supplier<String> supplier) {
assertFalse(actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(boolean actual, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isFalse();
}
}
static final class AssertThatIsNull {
@BeforeTemplate
void before(Object actual) {
assertNull(actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual) {
assertThat(actual).isNull();
}
}
static final class AssertThatWithFailMessageStringIsNull {
@BeforeTemplate
void before(Object actual, String message) {
assertNull(actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, String message) {
assertThat(actual).withFailMessage(message).isNull();
}
}
static final class AssertThatWithFailMessageSupplierIsNull {
@BeforeTemplate
void before(Object actual, Supplier<String> supplier) {
assertNull(actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isNull();
}
}
static final class AssertThatIsNotNull {
@BeforeTemplate
void before(Object actual) {
assertNotNull(actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual) {
assertThat(actual).isNotNull();
}
}
static final class AssertThatWithFailMessageStringIsNotNull {
@BeforeTemplate
void before(Object actual, String message) {
assertNotNull(actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, String message) {
assertThat(actual).withFailMessage(message).isNotNull();
}
}
static final class AssertThatWithFailMessageSupplierIsNotNull {
@BeforeTemplate
void before(Object actual, Supplier<String> supplier) {
assertNotNull(actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isNotNull();
}
}
static final class AssertThatIsSameAs {
@BeforeTemplate
void before(Object actual, Object expected) {
assertSame(expected, actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isSameAs(expected);
}
}
static final class AssertThatWithFailMessageStringIsSameAs {
@BeforeTemplate
void before(Object actual, Object expected, String message) {
assertSame(expected, actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isSameAs(expected);
}
}
static final class AssertThatWithFailMessageSupplierIsSameAs {
@BeforeTemplate
void before(Object actual, Object expected, Supplier<String> supplier) {
assertSame(expected, actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isSameAs(expected);
}
}
static final class AssertThatIsNotSameAs {
@BeforeTemplate
void before(Object actual, Object expected) {
assertNotSame(expected, actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isNotSameAs(expected);
}
}
static final class AssertThatWithFailMessageStringIsNotSameAs {
@BeforeTemplate
void before(Object actual, Object expected, String message) {
assertNotSame(expected, actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isNotSameAs(expected);
}
}
static final class AssertThatWithFailMessageSupplierIsNotSameAs {
@BeforeTemplate
void before(Object actual, Object expected, Supplier<String> supplier) {
assertNotSame(expected, actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isNotSameAs(expected);
}
}
static final class AssertThatThrownByIsExactlyInstanceOf<T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz) {
assertThrowsExactly(clazz, throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz) {
assertThatThrownBy(throwingCallable).isExactlyInstanceOf(clazz);
}
}
static final class AssertThatThrownByWithFailMessageStringIsExactlyInstanceOf<
T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz, String message) {
assertThrowsExactly(clazz, throwingCallable, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz, String message) {
assertThatThrownBy(throwingCallable).withFailMessage(message).isExactlyInstanceOf(clazz);
}
}
static final class AssertThatThrownByWithFailMessageSupplierIsExactlyInstanceOf<
T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz, Supplier<String> supplier) {
assertThrowsExactly(clazz, throwingCallable, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz, Supplier<String> supplier) {
assertThatThrownBy(throwingCallable).withFailMessage(supplier).isExactlyInstanceOf(clazz);
}
}
static final class AssertThatThrownByIsInstanceOf<T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz) {
assertThrows(clazz, throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz) {
assertThatThrownBy(throwingCallable).isInstanceOf(clazz);
}
}
static final class AssertThatThrownByWithFailMessageStringIsInstanceOf<T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz, String message) {
assertThrows(clazz, throwingCallable, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz, String message) {
assertThatThrownBy(throwingCallable).withFailMessage(message).isInstanceOf(clazz);
}
}
static final class AssertThatThrownByWithFailMessageSupplierIsInstanceOf<T extends Throwable> {
@BeforeTemplate
void before(Executable throwingCallable, Class<T> clazz, Supplier<String> supplier) {
assertThrows(clazz, throwingCallable, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Class<T> clazz, Supplier<String> supplier) {
assertThatThrownBy(throwingCallable).withFailMessage(supplier).isInstanceOf(clazz);
}
}
static final class AssertThatCodeDoesNotThrowAnyException {
@BeforeTemplate
void before(Executable throwingCallable) {
assertDoesNotThrow(throwingCallable);
}
@BeforeTemplate
void before(ThrowingSupplier<?> throwingCallable) {
assertDoesNotThrow(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable) {
assertThatCode(throwingCallable).doesNotThrowAnyException();
}
}
static final class AssertThatCodeWithFailMessageStringDoesNotThrowAnyException {
@BeforeTemplate
void before(Executable throwingCallable, String message) {
assertDoesNotThrow(throwingCallable, message);
}
@BeforeTemplate
void before(ThrowingSupplier<?> throwingCallable, String message) {
assertDoesNotThrow(throwingCallable, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, String message) {
assertThatCode(throwingCallable).withFailMessage(message).doesNotThrowAnyException();
}
}
static final class AssertThatCodeWithFailMessageSupplierDoesNotThrowAnyException {
@BeforeTemplate
void before(Executable throwingCallable, Supplier<String> supplier) {
assertDoesNotThrow(throwingCallable, supplier);
}
@BeforeTemplate
void before(ThrowingSupplier<?> throwingCallable, Supplier<String> supplier) {
assertDoesNotThrow(throwingCallable, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable throwingCallable, Supplier<String> supplier) {
assertThatCode(throwingCallable).withFailMessage(supplier).doesNotThrowAnyException();
}
}
static final class AssertThatIsInstanceOf<T> {
@BeforeTemplate
void before(Object actual, Class<T> clazz) {
assertInstanceOf(clazz, actual);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Class<T> clazz) {
assertThat(actual).isInstanceOf(clazz);
}
}
static final class AssertThatWithFailMessageStringIsInstanceOf<T> {
@BeforeTemplate
void before(Object actual, Class<T> clazz, String message) {
assertInstanceOf(clazz, actual, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Class<T> clazz, String message) {
assertThat(actual).withFailMessage(message).isInstanceOf(clazz);
}
}
static final class AssertThatWithFailMessageSupplierIsInstanceOf<T> {
@BeforeTemplate
void before(Object actual, Class<T> clazz, Supplier<String> supplier) {
assertInstanceOf(clazz, actual, supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(Object actual, Class<T> clazz, Supplier<String> supplier) {
assertThat(actual).withFailMessage(supplier).isInstanceOf(clazz);
}
}
}

View File

@@ -154,6 +154,22 @@ final class LongStreamRules {
}
}
/**
* Apply {@link LongStream#filter(LongPredicate)} before {@link LongStream#sorted()} to reduce the
* number of elements to sort.
*/
static final class LongStreamFilterSorted {
@BeforeTemplate
LongStream before(LongStream stream, LongPredicate predicate) {
return stream.sorted().filter(predicate);
}
@AfterTemplate
LongStream after(LongStream stream, LongPredicate predicate) {
return stream.filter(predicate).sorted();
}
}
/** In order to test whether a stream has any element, simply try to find one. */
static final class LongStreamIsEmpty {
@BeforeTemplate

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -7,7 +9,7 @@ import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Map} instances. */
@@ -31,16 +33,33 @@ final class MapRules {
static final class MapGetOrNull<K, V, T> {
@BeforeTemplate
@Nullable V before(Map<K, V> map, T key) {
@Nullable
V before(Map<K, V> map, T key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable V after(Map<K, V> map, T key) {
@Nullable
V after(Map<K, V> map, T key) {
return map.get(key);
}
}
/** Prefer {@link Map#getOrDefault(Object, Object)} over more contrived alternatives. */
// XXX: Note that `requireNonNullElse` throws an NPE if the second argument is `null`, while the
// alternative does not.
static final class MapGetOrDefault<K, V, T> {
@BeforeTemplate
V before(Map<K, V> map, T key, V defaultValue) {
return requireNonNullElse(map.get(key), defaultValue);
}
@AfterTemplate
V after(Map<K, V> map, T key, V defaultValue) {
return map.getOrDefault(key, defaultValue);
}
}
/** Prefer {@link Map#isEmpty()} over more contrived alternatives. */
static final class MapIsEmpty<K, V> {
@BeforeTemplate

View File

@@ -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 org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Multimap}s. */
@@ -50,7 +50,8 @@ 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);
}

View File

@@ -2,14 +2,18 @@ package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Objects.requireNonNullElse;
import static java.util.Objects.requireNonNullElseGet;
import com.google.common.base.MoreObjects;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import org.jspecify.nullness.Nullable;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with (possibly) null values. */
@@ -43,13 +47,18 @@ final class NullRules {
}
}
/** Prefer {@link Objects#requireNonNullElse(Object, Object)} over the Guava alternative. */
// XXX: This rule is not valid in case `second` is `@Nullable`: in that case the Guava variant
// will return `null`, while the JDK variant will throw an NPE.
/**
* Prefer {@link Objects#requireNonNullElse(Object, Object)} over non-JDK or more contrived
* alternatives.
*/
// XXX: This rule is not valid in case `second` is `@Nullable`: in that case the Guava and
// `Optional` variants will return `null`, where the `requireNonNullElse` alternative will throw
// an NPE.
static final class RequireNonNullElse<T> {
@BeforeTemplate
T before(T first, T second) {
return MoreObjects.firstNonNull(first, second);
return Refaster.anyOf(
MoreObjects.firstNonNull(first, second), Optional.ofNullable(first).orElse(second));
}
@AfterTemplate
@@ -59,6 +68,26 @@ final class NullRules {
}
}
/**
* Prefer {@link Objects#requireNonNullElseGet(Object, Supplier)} over more contrived
* alternatives.
*/
// XXX: This rule is not valid in case `supplier` yields `@Nullable` values: in that case the
// `Optional` variant will return `null`, where the `requireNonNullElseGet` alternative will throw
// an NPE.
static final class RequireNonNullElseGet<T, S extends T> {
@BeforeTemplate
T before(T object, Supplier<S> supplier) {
return Optional.ofNullable(object).orElseGet(supplier);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(T object, Supplier<S> supplier) {
return requireNonNullElseGet(object, supplier);
}
}
/** Prefer {@link Objects#isNull(Object)} over the equivalent lambda function. */
static final class IsNullFunction<T> {
@BeforeTemplate

View File

@@ -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 org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link Optional}s. */

View File

@@ -17,6 +17,7 @@ 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.Comparator;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.Callable;
@@ -26,6 +27,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -59,6 +61,45 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#empty()} over more contrived alternatives. */
static final class MonoEmpty<T> {
@BeforeTemplate
Mono<T> before() {
return Refaster.anyOf(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
@AfterTemplate
Mono<T> after() {
return Mono.empty();
}
}
/** Prefer {@link Mono#just(Object)} over more contrived alternatives. */
static final class MonoJust<T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.of(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.just(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Object)} over more contrived alternatives. */
static final class MonoJustOrEmpty<@Nullable T> {
@BeforeTemplate
Mono<T> before(T value) {
return Mono.justOrEmpty(Optional.ofNullable(value));
}
@AfterTemplate
Mono<T> after(T value) {
return Mono.justOrEmpty(value);
}
}
/** Prefer {@link Mono#justOrEmpty(Optional)} over more verbose alternatives. */
// XXX: If `optional` is a constant and effectively-final expression then the `Mono.defer` can be
// dropped. Should look into Refaster support for identifying this.
@@ -78,6 +119,40 @@ final class ReactorRules {
}
}
/**
* Try to avoid expressions of type {@code Optional<Mono<T>>}, but if you must map an {@link
* Optional} to this type, prefer using {@link Mono#just(Object)}.
*/
static final class OptionalMapMonoJust<T> {
@BeforeTemplate
Optional<Mono<T>> before(Optional<T> optional) {
return optional.map(Mono::justOrEmpty);
}
@AfterTemplate
Optional<Mono<T>> after(Optional<T> optional) {
return optional.map(Mono::just);
}
}
/**
* Prefer a {@link Mono#justOrEmpty(Optional)} and {@link Mono#switchIfEmpty(Mono)} chain over
* more contrived alternatives.
*
* <p>In particular, avoid mixing of the {@link Optional} and {@link Mono} APIs.
*/
static final class MonoFromOptionalSwitchIfEmpty<T> {
@BeforeTemplate
Mono<T> before(Optional<T> optional, Mono<T> mono) {
return optional.map(Mono::just).orElse(mono);
}
@AfterTemplate
Mono<T> after(Optional<T> optional, Mono<T> mono) {
return Mono.justOrEmpty(optional).switchIfEmpty(mono);
}
}
/**
* 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
@@ -290,13 +365,18 @@ final class ReactorRules {
}
}
/** Don't unnecessarily pass an empty publisher to {@link Mono#switchIfEmpty(Mono)}. */
static final class MonoSwitchIfEmptyOfEmptyPublisher<T> {
/** Don't unnecessarily transform a {@link Mono} to an equivalent instance. */
static final class MonoIdentity<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono) {
return mono.switchIfEmpty(Mono.empty());
}
@BeforeTemplate
Mono<@Nullable Void> before2(Mono<@Nullable Void> mono) {
return mono.then();
}
@AfterTemplate
Mono<T> after(Mono<T> mono) {
return mono;
@@ -600,6 +680,19 @@ final class ReactorRules {
}
}
/** Prefer direct invocation of {@link Mono#then()}} over more contrived alternatives. */
static final class MonoThen<T> {
@BeforeTemplate
Mono<@Nullable Void> before(Mono<T> mono) {
return mono.flux().then();
}
@AfterTemplate
Mono<@Nullable Void> after(Mono<T> mono) {
return mono.then();
}
}
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/
@@ -988,6 +1081,38 @@ final class ReactorRules {
}
}
/**
* Apply {@link Flux#filter(Predicate)} before {@link Flux#sort()} to reduce the number of
* elements to sort.
*/
static final class FluxFilterSort<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, Predicate<? super T> predicate) {
return flux.sort().filter(predicate);
}
@AfterTemplate
Flux<T> after(Flux<T> flux, Predicate<? super T> predicate) {
return flux.filter(predicate).sort();
}
}
/**
* Apply {@link Flux#filter(Predicate)} before {@link Flux#sort(Comparator)} to reduce the number
* of elements to sort.
*/
static final class FluxFilterSortWithComparator<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux, Predicate<? super T> predicate, Comparator<? super T> comparator) {
return flux.sort(comparator).filter(predicate);
}
@AfterTemplate
Flux<T> after(Flux<T> flux, Predicate<? super T> predicate, Comparator<? super T> comparator) {
return flux.filter(predicate).sort(comparator);
}
}
/** 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.

View File

@@ -9,7 +9,7 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

View File

@@ -77,6 +77,9 @@ final class StreamRules {
* Prefer {@link Arrays#stream(Object[])} over {@link Stream#of(Object[])}, as the former is
* clearer.
*/
// XXX: Introduce a `Matcher` that identifies `Refaster.asVarargs(...)` invocations and annotate
// the `array` parameter as `@NotMatches(IsRefasterAsVarargs.class)`. Then elsewhere
// `@SuppressWarnings("StreamOfArray")` annotations can be dropped.
static final class StreamOfArray<T> {
@BeforeTemplate
Stream<T> before(T[] array) {
@@ -164,6 +167,40 @@ final class StreamRules {
}
}
/**
* Apply {@link Stream#filter(Predicate)} before {@link Stream#sorted()} to reduce the number of
* elements to sort.
*/
static final class StreamFilterSorted<T> {
@BeforeTemplate
Stream<T> before(Stream<T> stream, Predicate<? super T> predicate) {
return stream.sorted().filter(predicate);
}
@AfterTemplate
Stream<T> after(Stream<T> stream, Predicate<? super T> predicate) {
return stream.filter(predicate).sorted();
}
}
/**
* Apply {@link Stream#filter(Predicate)} before {@link Stream#sorted(Comparator)} to reduce the
* number of elements to sort.
*/
static final class StreamFilterSortedWithComparator<T> {
@BeforeTemplate
Stream<T> before(
Stream<T> stream, Predicate<? super T> predicate, Comparator<? super T> comparator) {
return stream.sorted(comparator).filter(predicate);
}
@AfterTemplate
Stream<T> after(
Stream<T> stream, Predicate<? super T> predicate, Comparator<? super T> comparator) {
return stream.filter(predicate).sorted(comparator);
}
}
/**
* Where possible, clarify that a mapping operation will be applied only to a single stream
* element.

View File

@@ -16,7 +16,7 @@ import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to expressions dealing with {@link String}s. */

View File

@@ -28,6 +28,7 @@ import java.util.Set;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
import org.testng.Assert;
import org.testng.Assert.ThrowingRunnable;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/**
* Refaster rules that replace TestNG assertions with equivalent AssertJ assertions.
@@ -72,6 +73,7 @@ import org.testng.Assert.ThrowingRunnable;
// XXX: As-is these rules do not result in a complete migration:
// - Expressions containing comments are skipped due to a limitation of Refaster.
// - Assertions inside lambda expressions are also skipped. Unclear why.
@OnlineDocumentation
final class TestNGToAssertJRules {
private TestNGToAssertJRules() {}

View File

@@ -1,4 +1,4 @@
/** Picnic Refaster rules. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refasterrules;

View File

@@ -16,7 +16,10 @@ final class IdentityConversionTest {
void identification() {
compilationTestHelper
.addSourceLines(
"Foo.java",
"A.java",
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"",
"import com.google.common.collect.ImmutableBiMap;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableListMultimap;",
@@ -28,12 +31,14 @@ final class IdentityConversionTest {
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSetMultimap;",
"import com.google.common.collect.ImmutableTable;",
"import com.google.errorprone.matchers.Matcher;",
"import com.google.errorprone.matchers.Matchers;",
"import reactor.adapter.rxjava.RxJava2Adapter;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
"public final class A {",
" public void m() {",
" // BUG: Diagnostic contains:",
" Boolean b1 = Boolean.valueOf(Boolean.FALSE);",
" // BUG: Diagnostic contains:",
@@ -113,6 +118,13 @@ final class IdentityConversionTest {
" // BUG: Diagnostic contains:",
" short s4 = Short.valueOf(Short.MIN_VALUE);",
"",
" // BUG: Diagnostic contains:",
" String boolStr = Boolean.valueOf(Boolean.FALSE).toString();",
" int boolHash = Boolean.valueOf(false).hashCode();",
" // BUG: Diagnostic contains:",
" int byteHash = Byte.valueOf((Byte) Byte.MIN_VALUE).hashCode();",
" String byteStr = Byte.valueOf(Byte.MIN_VALUE).toString();",
"",
" String str1 = String.valueOf(0);",
" // BUG: Diagnostic contains:",
" String str2 = String.valueOf(\"1\");",
@@ -143,7 +155,15 @@ final class IdentityConversionTest {
" ImmutableTable<Object, Object, Object> o11 = ImmutableTable.copyOf(ImmutableTable.of());",
"",
" // BUG: Diagnostic contains:",
" Matcher allOf1 = Matchers.allOf(instanceMethod());",
" Matcher allOf2 = Matchers.allOf(instanceMethod(), staticMethod());",
" // BUG: Diagnostic contains:",
" Matcher anyOf1 = Matchers.anyOf(staticMethod());",
" Matcher anyOf2 = Matchers.anyOf(instanceMethod(), staticMethod());",
"",
" // BUG: Diagnostic contains:",
" Flux<Integer> flux1 = Flux.just(1).flatMap(e -> RxJava2Adapter.fluxToFlowable(Flux.just(2)));",
"",
" // BUG: Diagnostic contains:",
" Flux<Integer> flux2 = Flux.concat(Flux.just(1));",
" // BUG: Diagnostic contains:",
@@ -154,9 +174,9 @@ final class IdentityConversionTest {
" Flux<Integer> flux5 = Flux.merge(Flux.just(1));",
"",
" // BUG: Diagnostic contains:",
" Mono<Integer> m1 = Mono.from(Mono.just(1));",
" Mono<Integer> mono1 = Mono.from(Mono.just(1));",
" // BUG: Diagnostic contains:",
" Mono<Integer> m2 = Mono.fromDirect(Mono.just(1));",
" Mono<Integer> mono2 = Mono.fromDirect(Mono.just(1));",
" }",
"}")
.doTest();
@@ -167,12 +187,15 @@ final class IdentityConversionTest {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"Foo.java",
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.errorprone.matchers.Matcher;",
"import com.google.errorprone.matchers.Matchers;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"import org.reactivestreams.Publisher;",
@@ -180,8 +203,8 @@ final class IdentityConversionTest {
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
@@ -206,18 +229,23 @@ final class IdentityConversionTest {
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.copyOf(ImmutableSet.of());",
"",
" Matcher matcher = Matchers.allOf(staticMethod());",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"",
" void bar(Publisher<Integer> publisher) {}",
"}")
.addOutputLines(
"Foo.java",
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",
"import static org.mockito.Mockito.when;",
"",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.errorprone.matchers.Matcher;",
"import com.google.errorprone.matchers.Matchers;",
"import java.util.ArrayList;",
"import java.util.Collection;",
"import org.reactivestreams.Publisher;",
@@ -225,8 +253,8 @@ final class IdentityConversionTest {
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"public final class Foo {",
" public void foo() {",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.of();",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
@@ -251,6 +279,8 @@ final class IdentityConversionTest {
" Object o1 = ImmutableSet.copyOf(ImmutableList.of());",
" Object o2 = ImmutableSet.of();",
"",
" Matcher matcher = staticMethod();",
"",
" when(\"foo\".contains(\"f\")).thenAnswer(inv -> ImmutableSet.copyOf(ImmutableList.of(1)));",
" }",
"",
@@ -264,14 +294,14 @@ final class IdentityConversionTest {
refactoringTestHelper
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"Foo.java",
"A.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"",
"public final class Foo {",
" public void foo() {",
"public final class A {",
" public void m() {",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",
"",
@@ -280,14 +310,14 @@ final class IdentityConversionTest {
" }",
"}")
.addOutputLines(
"Foo.java",
"A.java",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.ArrayList;",
"",
"public final class Foo {",
" public void foo() {",
"public final class A {",
" public void m() {",
" @SuppressWarnings(\"IdentityConversion\")",
" ImmutableSet<Object> set1 = ImmutableSet.copyOf(ImmutableSet.of());",
" ImmutableSet<Object> set2 = ImmutableSet.copyOf(ImmutableList.of());",

View File

@@ -17,12 +17,28 @@ final class IsInstanceLambdaUsageTest {
.addSourceLines(
"A.java",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Integer localVariable = 0;",
"",
" Stream.of(0).map(i -> i + 1);",
" Stream.of(1).filter(Integer.class::isInstance);",
" Stream.of(2).filter(i -> i.getClass() instanceof Class);",
" Stream.of(3).filter(i -> localVariable instanceof Integer);",
" // XXX: Ideally this case is also flagged. Pick this up in the context of merging the",
" // `IsInstanceLambdaUsage` and `MethodReferenceUsage` checks, or introduce a separate check that",
" // simplifies unnecessary block lambda expressions.",
" Stream.of(4)",
" .filter(",
" i -> {",
" return localVariable instanceof Integer;",
" });",
" Flux.just(5, \"foo\").distinctUntilChanged(v -> v, (a, b) -> a instanceof Integer);",
"",
" // BUG: Diagnostic contains:",
" Stream.of(1).filter(i -> i instanceof Integer);",
" Stream.of(2).filter(Integer.class::isInstance);",
" Stream.of(6).filter(i -> i instanceof Integer);",
" }",
"}")
.doTest();

View File

@@ -91,6 +91,9 @@ final class JUnitMethodDeclarationTest {
" private void tearDown8() {}",
"",
" @Test",
" void test() {}",
"",
" @Test",
" void method1() {}",
"",
" @Test",
@@ -144,8 +147,13 @@ final class JUnitMethodDeclarationTest {
" void test5() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that a method named `overload` already exists in this",
" // class)",
" // BUG: Diagnostic contains: (but note that a method named `toString` is already defined in this",
" // class or a supertype)",
" void testToString() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that a method named `overload` is already defined in this",
" // class or a supertype)",
" void testOverload() {}",
"",
" void overload() {}",
@@ -155,8 +163,20 @@ final class JUnitMethodDeclarationTest {
" void testArguments() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that `public` is a reserved keyword)",
" // BUG: Diagnostic contains: (but note that `public` is not a valid identifier)",
" void testPublic() {}",
"",
" @Test",
" // BUG: Diagnostic contains: (but note that `null` is not a valid identifier)",
" void testNull() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void testRecord() {}",
"",
" @Test",
" // BUG: Diagnostic contains:",
" void testMethodThatIsOverriddenWithoutOverrideAnnotation() {}",
"}")
.addSourceLines(
"B.java",
@@ -218,6 +238,10 @@ final class JUnitMethodDeclarationTest {
"",
" @Override",
" @Test",
" void test() {}",
"",
" @Override",
" @Test",
" void method1() {}",
"",
" @Override",
@@ -267,6 +291,10 @@ final class JUnitMethodDeclarationTest {
"",
" @Override",
" @Test",
" void testToString() {}",
"",
" @Override",
" @Test",
" void testOverload() {}",
"",
" @Override",
@@ -279,6 +307,17 @@ final class JUnitMethodDeclarationTest {
" @Override",
" @Test",
" void testPublic() {}",
"",
" @Override",
" @Test",
" void testNull() {}",
"",
" @Override",
" @Test",
" void testRecord() {}",
"",
" @Test",
" void testMethodThatIsOverriddenWithoutOverrideAnnotation() {}",
"}")
.addSourceLines(
"C.java",
@@ -352,6 +391,9 @@ final class JUnitMethodDeclarationTest {
" protected void quux() {}",
"",
" @Test",
" public void testToString() {}",
"",
" @Test",
" public void testOverload() {}",
"",
" void overload() {}",
@@ -361,6 +403,9 @@ final class JUnitMethodDeclarationTest {
"",
" @Test",
" private void testClass() {}",
"",
" @Test",
" private void testTrue() {}",
"}")
.addOutputLines(
"A.java",
@@ -407,6 +452,9 @@ final class JUnitMethodDeclarationTest {
" void quux() {}",
"",
" @Test",
" void testToString() {}",
"",
" @Test",
" void testOverload() {}",
"",
" void overload() {}",
@@ -416,6 +464,9 @@ final class JUnitMethodDeclarationTest {
"",
" @Test",
" void testClass() {}",
"",
" @Test",
" void testTrue() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}

View File

@@ -39,4 +39,19 @@ final class NestedOptionalsTest {
"}")
.doTest();
}
@Test
void identificationOptionalTypeNotLoaded() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.time.Duration;",
"",
"class A {",
" void m() {",
" Duration.ofSeconds(1);",
" }",
"}")
.doTest();
}
}

View File

@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Test;
final class RequestParamTypeTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.setArgs(
"-XepOpt:RequestParamType:SupportedCustomTypes=com.google.common.collect.ImmutableSet,com.google.common.collect.ImmutableSortedMultiset");
@Test
void identification() {
@@ -19,7 +23,7 @@ final class RequestParamTypeTest {
"import java.util.List;",
"import java.util.Map;",
"import java.util.Set;",
"import org.jspecify.nullness.Nullable;",
"import org.jspecify.annotations.Nullable;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PostMapping;",
@@ -63,4 +67,53 @@ final class RequestParamTypeTest {
"}")
.doTest();
}
@Test
void identificationRestricted() {
restrictedCompilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableBiMap;",
"import com.google.common.collect.ImmutableCollection;",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableMultiset;",
"import com.google.common.collect.ImmutableSet;",
"import com.google.common.collect.ImmutableSortedMultiset;",
"import com.google.common.collect.ImmutableSortedSet;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.RequestParam;",
"",
"interface A {",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A immutableCollection(@RequestParam ImmutableCollection<String> param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A immutableList(@RequestParam ImmutableList<String> param);",
"",
" @GetMapping",
" A immutableSet(@RequestParam ImmutableSet<String> param);",
"",
" @GetMapping",
" A immutableSortedSet(@RequestParam ImmutableSortedSet<String> param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A immutableMultiset(@RequestParam ImmutableMultiset<String> param);",
"",
" @GetMapping",
" A immutableSortedMultiset(@RequestParam ImmutableSortedMultiset<String> param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A immutableMap(@RequestParam ImmutableMap<String, String> param);",
"",
" @GetMapping",
" // BUG: Diagnostic contains:",
" A immutableBiMap(@RequestParam ImmutableBiMap<String, String> param);",
"}")
.doTest();
}
}

View File

@@ -0,0 +1,132 @@
package tech.picnic.errorprone.bugpatterns;
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;
final class StringCaseLocaleUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StringCaseLocaleUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(StringCaseLocaleUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.util.Locale.ROOT;",
"",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.ROOT);",
" \"a\".toUpperCase(Locale.ROOT);",
" \"b\".toLowerCase(ROOT);",
" \"b\".toUpperCase(ROOT);",
" \"c\".toLowerCase(Locale.getDefault());",
" \"c\".toUpperCase(Locale.getDefault());",
" \"d\".toLowerCase(Locale.ENGLISH);",
" \"d\".toUpperCase(Locale.ENGLISH);",
" \"e\".toLowerCase(new Locale(\"foo\"));",
" \"e\".toUpperCase(new Locale(\"foo\"));",
"",
" // BUG: Diagnostic contains:",
" \"f\".toLowerCase();",
" // BUG: Diagnostic contains:",
" \"g\".toUpperCase();",
"",
" String h = \"h\";",
" // BUG: Diagnostic contains:",
" h.toLowerCase();",
" String i = \"i\";",
" // BUG: Diagnostic contains:",
" i.toUpperCase();",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase(/* Comment with parens: (). */ );",
" \"b\".toUpperCase();",
" \"c\".toLowerCase().toString();",
"",
" toString().toLowerCase();",
" toString().toUpperCase /* Comment with parens: (). */();",
"",
" this.toString().toLowerCase() /* Comment with parens: (). */;",
" this.toString().toUpperCase();",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(/* Comment with parens: (). */ Locale.ROOT);",
" \"b\".toUpperCase(Locale.ROOT);",
" \"c\".toLowerCase(Locale.ROOT).toString();",
"",
" toString().toLowerCase(Locale.ROOT);",
" toString().toUpperCase /* Comment with parens: (). */(Locale.ROOT);",
"",
" this.toString().toLowerCase(Locale.ROOT) /* Comment with parens: (). */;",
" this.toString().toUpperCase(Locale.ROOT);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",
"class A {",
" void m() {",
" \"a\".toLowerCase();",
" \"b\".toUpperCase(/* Comment with parens: (). */ );",
" \"c\".toLowerCase().toString();",
"",
" toString().toLowerCase();",
" toString().toUpperCase /* Comment with parens: (). */();",
"",
" this.toString().toLowerCase() /* Comment with parens: (). */;",
" this.toString().toUpperCase();",
" }",
"}")
.addOutputLines(
"A.java",
"import java.util.Locale;",
"",
"class A {",
" void m() {",
" \"a\".toLowerCase(Locale.getDefault());",
" \"b\".toUpperCase(/* Comment with parens: (). */ Locale.getDefault());",
" \"c\".toLowerCase(Locale.getDefault()).toString();",
"",
" toString().toLowerCase(Locale.getDefault());",
" toString().toUpperCase /* Comment with parens: (). */(Locale.getDefault());",
"",
" this.toString().toLowerCase(Locale.getDefault()) /* Comment with parens: (). */;",
" this.toString().toUpperCase(Locale.getDefault());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,31 @@
package tech.picnic.errorprone.bugpatterns.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.ErrorProneOptions;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
final class FlagsTest {
private static Stream<Arguments> getListTestCases() {
/* { args, flag, expected } */
return Stream.of(
arguments(ImmutableList.of(), "Foo", ImmutableList.of()),
arguments(ImmutableList.of("-XepOpt:Foo=bar,baz"), "Qux", ImmutableList.of()),
arguments(ImmutableList.of("-XepOpt:Foo="), "Foo", ImmutableList.of()),
arguments(ImmutableList.of("-XepOpt:Foo=bar"), "Foo", ImmutableList.of("bar")),
arguments(ImmutableList.of("-XepOpt:Foo=bar,baz"), "Foo", ImmutableList.of("bar", "baz")),
arguments(ImmutableList.of("-XepOpt:Foo=,"), "Foo", ImmutableList.of("", "")));
}
@MethodSource("getListTestCases")
@ParameterizedTest
void getList(ImmutableList<String> args, String flag, ImmutableList<String> expected) {
assertThat(Flags.getList(ErrorProneOptions.processArgs(args).getFlags(), flag))
.containsExactlyElementsOf(expected);
}
}

View File

@@ -0,0 +1,34 @@
package tech.picnic.errorprone.bugpatterns.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
final class JavaKeywordsTest {
private static Stream<Arguments> isValidIdentifierTestCases() {
/* { str, expected } */
return Stream.of(
arguments("", false),
arguments("public", false),
arguments("true", false),
arguments("false", false),
arguments("null", false),
arguments("0", false),
arguments("\0", false),
arguments("a%\0", false),
arguments("a", true),
arguments("a0", true),
arguments("_a0", true),
arguments("test", true));
}
@MethodSource("isValidIdentifierTestCases")
@ParameterizedTest
void isValidIdentifier(String str, boolean expected) {
assertThat(JavaKeywords.isValidIdentifier(str)).isEqualTo(expected);
}
}

View File

@@ -25,46 +25,6 @@ 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())
@@ -159,8 +119,8 @@ final class MoreTypesTest {
}
/**
* A {@link BugChecker} that flags method invocations that are a subtype of any type contained in
* {@link #TYPES}.
* A {@link BugChecker} that flags method invocations that are a subtype of any type defined by
* {@link #getTestTypes()}.
*/
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
public static final class SubtypeFlagger extends BugChecker
@@ -173,7 +133,7 @@ final class MoreTypesTest {
List<String> matches = new ArrayList<>();
for (Supplier<Type> type : TYPES) {
for (Supplier<Type> type : getTestTypes()) {
Type testType = type.get(state);
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
matches.add(Signatures.prettyType(testType));
@@ -184,5 +144,52 @@ final class MoreTypesTest {
? Description.NO_MATCH
: buildDescription(tree).setMessage(matches.toString()).build();
}
/**
* Returns the type suppliers under test.
*
* @implNote The return value of this method should not be assigned to a field, as that would
* prevent mutations introduced by Pitest from being killed.
*/
private static ImmutableSet<Supplier<Type>> getTestTypes() {
return 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"))))));
}
}
}

View File

@@ -50,6 +50,7 @@ final class RefasterRulesTest {
ImmutableSortedSetRules.class,
IntStreamRules.class,
JUnitRules.class,
JUnitToAssertJRules.class,
LongStreamRules.class,
MapEntryRules.class,
MapRules.class,

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.valueOf(10), BigDecimal.valueOf(10L), new BigDecimal("10"));
}
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(new BigDecimal(0), new BigDecimal(0L));
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(new BigDecimal(2), new BigDecimal(2L), new BigDecimal(2.0));
}
}

View File

@@ -17,7 +17,7 @@ final class BigDecimalRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableSet.of(BigDecimal.TEN, BigDecimal.TEN, BigDecimal.TEN);
}
ImmutableSet<BigDecimal> testBigDecimalFactoryMethod() {
return ImmutableSet.of(BigDecimal.valueOf(0), BigDecimal.valueOf(0L));
ImmutableSet<BigDecimal> testBigDecimalValueOf() {
return ImmutableSet.of(BigDecimal.valueOf(2), BigDecimal.valueOf(2L), BigDecimal.valueOf(2.0));
}
}

View File

@@ -186,4 +186,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
? Optional.ofNullable(new LinkedList<String>().remove())
: Optional.empty());
}
void testCollectionForEach() {
ImmutableSet.of(1).stream().forEach(String::valueOf);
}
}

View File

@@ -136,4 +136,8 @@ final class CollectionRulesTest implements RefasterRuleCollectionTestCase {
Optional.ofNullable(new LinkedList<String>().poll()),
Optional.ofNullable(new LinkedList<String>().poll()));
}
void testCollectionForEach() {
ImmutableSet.of(1).forEach(String::valueOf);
}
}

View File

@@ -4,30 +4,42 @@ import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(
String::compareTo,
Comparator.comparing(identity()),
Comparator.comparing(s -> s),
Collections.<String>reverseOrder(reverseOrder()),
Comparator.<String>reverseOrder().reversed());
}
Comparator<String> testReverseOrder() {
return Comparator.<String>naturalOrder().reversed();
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Collections.reverseOrder(),
Collections.<String>reverseOrder(naturalOrder()),
Comparator.<String>naturalOrder().reversed());
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -77,8 +89,24 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(s -> s));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of(
Comparator.<String>naturalOrder().compare("foo", "bar"),
Comparator.<String>reverseOrder().compare("baz", "qux"));
}
int testMinOfVarargs() {
return Stream.of(1, 2).min(naturalOrder()).orElseThrow();
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") <= 0 ? "a" : "b",
"a".compareTo("b") > 0 ? "b" : "a",
"a".compareTo("b") < 0 ? "a" : "b",
"a".compareTo("b") >= 0 ? "b" : "a",
Comparators.min("a", "b", naturalOrder()),
Comparators.max("a", "b", reverseOrder()),
Collections.min(Arrays.asList("a", "b")),
Collections.min(ImmutableList.of("a", "b")),
Collections.min(ImmutableSet.of("a", "b")));
@@ -86,13 +114,27 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Collections.min(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.min(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.min(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "b" : "a",
Collections.min(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.min(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.min(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
int testMaxOfVarargs() {
return Stream.of(1, 2).max(naturalOrder()).orElseThrow();
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
"a".compareTo("b") >= 0 ? "a" : "b",
"a".compareTo("b") < 0 ? "b" : "a",
"a".compareTo("b") > 0 ? "a" : "b",
"a".compareTo("b") <= 0 ? "b" : "a",
Comparators.max("a", "b", naturalOrder()),
Comparators.min("a", "b", reverseOrder()),
Collections.max(Arrays.asList("a", "b")),
Collections.max(ImmutableList.of("a", "b")),
Collections.max(ImmutableSet.of("a", "b")));
@@ -100,9 +142,13 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Collections.max(Arrays.asList(new Object(), new Object()), (a, b) -> -1),
Collections.max(ImmutableList.of(new Object(), new Object()), (a, b) -> 0),
Collections.max(ImmutableSet.of(new Object(), new Object()), (a, b) -> 1));
Comparator.comparingInt(String::length).compare("a", "b") >= 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") < 0 ? "b" : "a",
Comparator.comparingInt(String::length).compare("a", "b") > 0 ? "a" : "b",
Comparator.comparingInt(String::length).compare("a", "b") <= 0 ? "b" : "a",
Collections.max(Arrays.asList("a", "b"), (a, b) -> -1),
Collections.max(ImmutableList.of("a", "b"), (a, b) -> 0),
Collections.max(ImmutableSet.of("a", "b"), (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -11,21 +11,29 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Collections.class, ImmutableList.class, ImmutableSet.class, identity());
Arrays.class,
Collections.class,
ImmutableList.class,
ImmutableSet.class,
Stream.class,
identity());
}
ImmutableSet<Comparator<String>> testNaturalOrder() {
return ImmutableSet.of(naturalOrder(), naturalOrder(), naturalOrder());
return ImmutableSet.of(
naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder(), naturalOrder());
}
Comparator<String> testReverseOrder() {
return reverseOrder();
ImmutableSet<Comparator<String>> testReverseOrder() {
return ImmutableSet.of(
Comparator.reverseOrder(), Comparator.reverseOrder(), Comparator.reverseOrder());
}
ImmutableSet<Comparator<String>> testCustomComparator() {
@@ -68,28 +76,64 @@ final class ComparatorRulesTest implements RefasterRuleCollectionTestCase {
Comparator.<String>naturalOrder().thenComparing(naturalOrder()));
}
ImmutableSet<Integer> testCompareTo() {
return ImmutableSet.of("foo".compareTo("bar"), "qux".compareTo("baz"));
}
int testMinOfVarargs() {
return Collections.min(Arrays.asList(1, 2), naturalOrder());
}
ImmutableSet<String> testMinOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.min("a", "b"), Comparators.min("a", "b"), Comparators.min("a", "b"));
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("b", "a"),
Comparators.min("b", "a"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"),
Comparators.min("a", "b"));
}
ImmutableSet<Object> testMinOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.min(new Object(), new Object(), (a, b) -> -1),
Comparators.min(new Object(), new Object(), (a, b) -> 0),
Comparators.min(new Object(), new Object(), (a, b) -> 1));
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("b", "a", Comparator.comparingInt(String::length)),
Comparators.min("a", "b", (a, b) -> -1),
Comparators.min("a", "b", (a, b) -> 0),
Comparators.min("a", "b", (a, b) -> 1));
}
int testMaxOfVarargs() {
return Collections.max(Arrays.asList(1, 2), naturalOrder());
}
ImmutableSet<String> testMaxOfPairNaturalOrder() {
return ImmutableSet.of(
Comparators.max("a", "b"), Comparators.max("a", "b"), Comparators.max("a", "b"));
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("b", "a"),
Comparators.max("b", "a"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"),
Comparators.max("a", "b"));
}
ImmutableSet<Object> testMaxOfPairCustomOrder() {
return ImmutableSet.of(
Comparators.max(new Object(), new Object(), (a, b) -> -1),
Comparators.max(new Object(), new Object(), (a, b) -> 0),
Comparators.max(new Object(), new Object(), (a, b) -> 1));
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("b", "a", Comparator.comparingInt(String::length)),
Comparators.max("a", "b", (a, b) -> -1),
Comparators.max("a", "b", (a, b) -> 0),
Comparators.max("a", "b", (a, b) -> 1));
}
BinaryOperator<String> testComparatorsMin() {

View File

@@ -46,6 +46,10 @@ final class DoubleStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToDouble(v -> DoubleStream.of(v * v).flatMap(DoubleStream::of));
}
DoubleStream testDoubleStreamFilterSorted() {
return DoubleStream.of(1, 4, 3, 2).sorted().filter(d -> d % 2 == 0);
}
ImmutableSet<Boolean> testDoubleStreamIsEmpty() {
return ImmutableSet.of(
DoubleStream.of(1).count() == 0,

View File

@@ -46,6 +46,10 @@ final class DoubleStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToDouble(v -> DoubleStream.of(v * v)).flatMap(DoubleStream::of);
}
DoubleStream testDoubleStreamFilterSorted() {
return DoubleStream.of(1, 4, 3, 2).filter(d -> d % 2 == 0).sorted();
}
ImmutableSet<Boolean> testDoubleStreamIsEmpty() {
return ImmutableSet.of(
DoubleStream.of(1).findAny().isEmpty(),

View File

@@ -50,6 +50,10 @@ final class IntStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToInt(v -> IntStream.of(v * v).flatMap(IntStream::of));
}
IntStream testIntStreamFilterSorted() {
return IntStream.of(1, 4, 3, 2).sorted().filter(i -> i % 2 == 0);
}
ImmutableSet<Boolean> testIntStreamIsEmpty() {
return ImmutableSet.of(
IntStream.of(1).count() == 0,

View File

@@ -50,6 +50,10 @@ final class IntStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToInt(v -> IntStream.of(v * v)).flatMap(IntStream::of);
}
IntStream testIntStreamFilterSorted() {
return IntStream.of(1, 4, 3, 2).filter(i -> i % 2 == 0).sorted();
}
ImmutableSet<Boolean> testIntStreamIsEmpty() {
return ImmutableSet.of(
IntStream.of(1).findAny().isEmpty(),

View File

@@ -0,0 +1,173 @@
package tech.picnic.errorprone.refasterrules;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Assertions;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Assertions.class,
assertDoesNotThrow(() -> null),
assertInstanceOf(null, null),
assertThrows(null, null),
assertThrowsExactly(null, null),
(Runnable) () -> assertFalse(true),
(Runnable) () -> assertNotNull(null),
(Runnable) () -> assertNotSame(null, null),
(Runnable) () -> assertNull(null),
(Runnable) () -> assertSame(null, null),
(Runnable) () -> assertTrue(true));
}
void testThrowNewAssertionError() {
Assertions.fail();
}
Object testFailWithMessage() {
return Assertions.fail("foo");
}
Object testFailWithMessageAndThrowable() {
return Assertions.fail("foo", new IllegalStateException());
}
void testFailWithThrowable() {
Assertions.fail(new IllegalStateException());
}
void testAssertThatIsTrue() {
assertTrue(true);
}
void testAssertThatWithFailMessageStringIsTrue() {
assertTrue(true, "foo");
}
void testAssertThatWithFailMessageSupplierIsTrue() {
assertTrue(true, () -> "foo");
}
void testAssertThatIsFalse() {
assertFalse(true);
}
void testAssertThatWithFailMessageStringIsFalse() {
assertFalse(true, "foo");
}
void testAssertThatWithFailMessageSupplierIsFalse() {
assertFalse(true, () -> "foo");
}
void testAssertThatIsNull() {
assertNull(new Object());
}
void testAssertThatWithFailMessageStringIsNull() {
assertNull(new Object(), "foo");
}
void testAssertThatWithFailMessageSupplierIsNull() {
assertNull(new Object(), () -> "foo");
}
void testAssertThatIsNotNull() {
assertNotNull(new Object());
}
void testAssertThatWithFailMessageStringIsNotNull() {
assertNotNull(new Object(), "foo");
}
void testAssertThatWithFailMessageSupplierIsNotNull() {
assertNotNull(new Object(), () -> "foo");
}
void testAssertThatIsSameAs() {
assertSame("foo", "bar");
}
void testAssertThatWithFailMessageStringIsSameAs() {
assertSame("foo", "bar", "baz");
}
void testAssertThatWithFailMessageSupplierIsSameAs() {
assertSame("foo", "bar", () -> "baz");
}
void testAssertThatIsNotSameAs() {
assertNotSame("foo", "bar");
}
void testAssertThatWithFailMessageStringIsNotSameAs() {
assertNotSame("foo", "bar", "baz");
}
void testAssertThatWithFailMessageSupplierIsNotSameAs() {
assertNotSame("foo", "bar", () -> "baz");
}
void testAssertThatThrownByIsExactlyInstanceOf() {
assertThrowsExactly(IllegalStateException.class, () -> {});
}
void testAssertThatThrownByWithFailMessageStringIsExactlyInstanceOf() {
assertThrowsExactly(IllegalStateException.class, () -> {}, "foo");
}
void testAssertThatThrownByWithFailMessageSupplierIsExactlyInstanceOf() {
assertThrowsExactly(IllegalStateException.class, () -> {}, () -> "foo");
}
void testAssertThatThrownByIsInstanceOf() {
assertThrows(IllegalStateException.class, () -> {});
}
void testAssertThatThrownByWithFailMessageStringIsInstanceOf() {
assertThrows(IllegalStateException.class, () -> {}, "foo");
}
void testAssertThatThrownByWithFailMessageSupplierIsInstanceOf() {
assertThrows(IllegalStateException.class, () -> {}, () -> "foo");
}
void testAssertThatCodeDoesNotThrowAnyException() {
assertDoesNotThrow(() -> {});
assertDoesNotThrow(() -> toString());
}
void testAssertThatCodeWithFailMessageStringDoesNotThrowAnyException() {
assertDoesNotThrow(() -> {}, "foo");
assertDoesNotThrow(() -> toString(), "bar");
}
void testAssertThatCodeWithFailMessageSupplierDoesNotThrowAnyException() {
assertDoesNotThrow(() -> {}, () -> "foo");
assertDoesNotThrow(() -> toString(), () -> "bar");
}
void testAssertThatIsInstanceOf() {
assertInstanceOf(Object.class, new Object());
}
void testAssertThatWithFailMessageStringIsInstanceOf() {
assertInstanceOf(Object.class, new Object(), "foo");
}
void testAssertThatWithFailMessageSupplierIsInstanceOf() {
assertInstanceOf(Object.class, new Object(), () -> "foo");
}
}

View File

@@ -0,0 +1,183 @@
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Assertions;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class JUnitToAssertJRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Assertions.class,
assertDoesNotThrow(() -> null),
assertInstanceOf(null, null),
assertThrows(null, null),
assertThrowsExactly(null, null),
(Runnable) () -> assertFalse(true),
(Runnable) () -> assertNotNull(null),
(Runnable) () -> assertNotSame(null, null),
(Runnable) () -> assertNull(null),
(Runnable) () -> assertSame(null, null),
(Runnable) () -> assertTrue(true));
}
void testThrowNewAssertionError() {
throw new AssertionError();
}
Object testFailWithMessage() {
return fail("foo");
}
Object testFailWithMessageAndThrowable() {
return fail("foo", new IllegalStateException());
}
void testFailWithThrowable() {
throw new AssertionError(new IllegalStateException());
}
void testAssertThatIsTrue() {
assertThat(true).isTrue();
}
void testAssertThatWithFailMessageStringIsTrue() {
assertThat(true).withFailMessage("foo").isTrue();
}
void testAssertThatWithFailMessageSupplierIsTrue() {
assertThat(true).withFailMessage(() -> "foo").isTrue();
}
void testAssertThatIsFalse() {
assertThat(true).isFalse();
}
void testAssertThatWithFailMessageStringIsFalse() {
assertThat(true).withFailMessage("foo").isFalse();
}
void testAssertThatWithFailMessageSupplierIsFalse() {
assertThat(true).withFailMessage(() -> "foo").isFalse();
}
void testAssertThatIsNull() {
assertThat(new Object()).isNull();
}
void testAssertThatWithFailMessageStringIsNull() {
assertThat(new Object()).withFailMessage("foo").isNull();
}
void testAssertThatWithFailMessageSupplierIsNull() {
assertThat(new Object()).withFailMessage(() -> "foo").isNull();
}
void testAssertThatIsNotNull() {
assertThat(new Object()).isNotNull();
}
void testAssertThatWithFailMessageStringIsNotNull() {
assertThat(new Object()).withFailMessage("foo").isNotNull();
}
void testAssertThatWithFailMessageSupplierIsNotNull() {
assertThat(new Object()).withFailMessage(() -> "foo").isNotNull();
}
void testAssertThatIsSameAs() {
assertThat("bar").isSameAs("foo");
}
void testAssertThatWithFailMessageStringIsSameAs() {
assertThat("bar").withFailMessage("baz").isSameAs("foo");
}
void testAssertThatWithFailMessageSupplierIsSameAs() {
assertThat("bar").withFailMessage(() -> "baz").isSameAs("foo");
}
void testAssertThatIsNotSameAs() {
assertThat("bar").isNotSameAs("foo");
}
void testAssertThatWithFailMessageStringIsNotSameAs() {
assertThat("bar").withFailMessage("baz").isNotSameAs("foo");
}
void testAssertThatWithFailMessageSupplierIsNotSameAs() {
assertThat("bar").withFailMessage(() -> "baz").isNotSameAs("foo");
}
void testAssertThatThrownByIsExactlyInstanceOf() {
assertThatThrownBy(() -> {}).isExactlyInstanceOf(IllegalStateException.class);
}
void testAssertThatThrownByWithFailMessageStringIsExactlyInstanceOf() {
assertThatThrownBy(() -> {})
.withFailMessage("foo")
.isExactlyInstanceOf(IllegalStateException.class);
}
void testAssertThatThrownByWithFailMessageSupplierIsExactlyInstanceOf() {
assertThatThrownBy(() -> {})
.withFailMessage(() -> "foo")
.isExactlyInstanceOf(IllegalStateException.class);
}
void testAssertThatThrownByIsInstanceOf() {
assertThatThrownBy(() -> {}).isInstanceOf(IllegalStateException.class);
}
void testAssertThatThrownByWithFailMessageStringIsInstanceOf() {
assertThatThrownBy(() -> {}).withFailMessage("foo").isInstanceOf(IllegalStateException.class);
}
void testAssertThatThrownByWithFailMessageSupplierIsInstanceOf() {
assertThatThrownBy(() -> {})
.withFailMessage(() -> "foo")
.isInstanceOf(IllegalStateException.class);
}
void testAssertThatCodeDoesNotThrowAnyException() {
assertThatCode(() -> {}).doesNotThrowAnyException();
assertThatCode(() -> toString()).doesNotThrowAnyException();
}
void testAssertThatCodeWithFailMessageStringDoesNotThrowAnyException() {
assertThatCode(() -> {}).withFailMessage("foo").doesNotThrowAnyException();
assertThatCode(() -> toString()).withFailMessage("bar").doesNotThrowAnyException();
}
void testAssertThatCodeWithFailMessageSupplierDoesNotThrowAnyException() {
assertThatCode(() -> {}).withFailMessage(() -> "foo").doesNotThrowAnyException();
assertThatCode(() -> toString()).withFailMessage(() -> "bar").doesNotThrowAnyException();
}
void testAssertThatIsInstanceOf() {
assertThat(new Object()).isInstanceOf(Object.class);
}
void testAssertThatWithFailMessageStringIsInstanceOf() {
assertThat(new Object()).withFailMessage("foo").isInstanceOf(Object.class);
}
void testAssertThatWithFailMessageSupplierIsInstanceOf() {
assertThat(new Object()).withFailMessage(() -> "foo").isInstanceOf(Object.class);
}
}

View File

@@ -50,6 +50,10 @@ final class LongStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToLong(v -> LongStream.of(v * v).flatMap(LongStream::of));
}
LongStream testLongStreamFilterSorted() {
return LongStream.of(1, 4, 3, 2).sorted().filter(l -> l % 2 == 0);
}
ImmutableSet<Boolean> testLongStreamIsEmpty() {
return ImmutableSet.of(
LongStream.of(1).count() == 0,

View File

@@ -50,6 +50,10 @@ final class LongStreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of(1).flatMapToLong(v -> LongStream.of(v * v)).flatMap(LongStream::of);
}
LongStream testLongStreamFilterSorted() {
return LongStream.of(1, 4, 3, 2).filter(l -> l % 2 == 0).sorted();
}
ImmutableSet<Boolean> testLongStreamIsEmpty() {
return ImmutableSet.of(
LongStream.of(1).findAny().isEmpty(),

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
@@ -11,7 +13,7 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
return ImmutableSet.of(HashMap.class, requireNonNullElse(null, null));
}
Map<RoundingMode, String> testCreateEnumMap() {
@@ -22,6 +24,10 @@ final class MapRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableMap.of(1, "foo").getOrDefault("bar", null);
}
String testMapGetOrDefault() {
return requireNonNullElse(ImmutableMap.of(1, "foo").get("bar"), "baz");
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).keySet().isEmpty(),

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.math.RoundingMode;
@@ -12,7 +14,7 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class MapRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(HashMap.class);
return ImmutableSet.of(HashMap.class, requireNonNullElse(null, null));
}
Map<RoundingMode, String> testCreateEnumMap() {
@@ -23,6 +25,10 @@ final class MapRulesTest implements RefasterRuleCollectionTestCase {
return ImmutableMap.of(1, "foo").get("bar");
}
String testMapGetOrDefault() {
return ImmutableMap.of(1, "foo").getOrDefault("bar", "baz");
}
ImmutableSet<Boolean> testMapIsEmpty() {
return ImmutableSet.of(
ImmutableMap.of("foo", 1).isEmpty(),

View File

@@ -3,13 +3,14 @@ package tech.picnic.errorprone.refasterrules;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class NullRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(MoreObjects.class);
return ImmutableSet.of(MoreObjects.class, Optional.class);
}
boolean testIsNull() {
@@ -20,8 +21,13 @@ final class NullRulesTest implements RefasterRuleCollectionTestCase {
return Objects.nonNull("foo");
}
String testRequireNonNullElse() {
return MoreObjects.firstNonNull("foo", "bar");
ImmutableSet<String> testRequireNonNullElse() {
return ImmutableSet.of(
MoreObjects.firstNonNull("foo", "bar"), Optional.ofNullable("baz").orElse("qux"));
}
String testRequireNonNullElseGet() {
return Optional.ofNullable("foo").orElseGet(() -> "bar");
}
long testIsNullFunction() {

View File

@@ -1,17 +1,19 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import static java.util.Objects.requireNonNullElseGet;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class NullRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(MoreObjects.class);
return ImmutableSet.of(MoreObjects.class, Optional.class);
}
boolean testIsNull() {
@@ -22,8 +24,12 @@ final class NullRulesTest implements RefasterRuleCollectionTestCase {
return "foo" != null;
}
String testRequireNonNullElse() {
return requireNonNullElse("foo", "bar");
ImmutableSet<String> testRequireNonNullElse() {
return ImmutableSet.of(requireNonNullElse("foo", "bar"), requireNonNullElse("baz", "qux"));
}
String testRequireNonNullElseGet() {
return requireNonNullElseGet("foo", () -> "bar");
}
long testIsNullFunction() {

View File

@@ -1,5 +1,6 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
@@ -34,12 +35,32 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromCallable(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.justOrEmpty(null), Mono.justOrEmpty(Optional.empty()));
}
Mono<Integer> testMonoJust() {
return Mono.justOrEmpty(Optional.of(1));
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(Optional.ofNullable(1));
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.fromCallable(() -> Optional.of(1).orElse(null)),
Mono.fromSupplier(() -> Optional.of(2).orElse(null)));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::justOrEmpty);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Optional.of(1).map(Mono::just).orElse(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.just("foo").zipWith(Mono.just(1));
}
@@ -94,8 +115,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just("baz").switchIfEmpty(Flux.just("qux")));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1).switchIfEmpty(Mono.empty());
ImmutableSet<Mono<?>> testMonoIdentity() {
return ImmutableSet.of(Mono.just(1).switchIfEmpty(Mono.empty()), Mono.<Void>empty().then());
}
ImmutableSet<Flux<Integer>> testFluxSwitchIfEmptyOfEmptyPublisher() {
@@ -200,6 +221,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.concat(Mono.just("baz")));
}
Mono<Void> testMonoThen() {
return Mono.just("foo").flux().then();
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty());
}
@@ -307,6 +332,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).onErrorReturn(IllegalArgumentException.class::isInstance, 2);
}
Flux<Integer> testFluxFilterSort() {
return Flux.just(1, 4, 3, 2).sort().filter(i -> i % 2 == 0);
}
Flux<Integer> testFluxFilterSortWithComparator() {
return Flux.just(1, 4, 3, 2).sort(reverseOrder()).filter(i -> i % 2 == 0);
}
ImmutableSet<Context> testContextEmpty() {
return ImmutableSet.of(Context.of(new HashMap<>()), Context.of(ImmutableMap.of()));
}

View File

@@ -1,6 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.MoreCollectors.toOptional;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Function.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
@@ -37,12 +38,32 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.fromSupplier(this::toString));
}
ImmutableSet<Mono<String>> testMonoEmpty() {
return ImmutableSet.of(Mono.empty(), Mono.empty());
}
Mono<Integer> testMonoJust() {
return Mono.just(1);
}
Mono<Integer> testMonoJustOrEmpty() {
return Mono.justOrEmpty(1);
}
ImmutableSet<Mono<Integer>> testMonoFromOptional() {
return ImmutableSet.of(
Mono.defer(() -> Mono.justOrEmpty(Optional.of(1))),
Mono.defer(() -> Mono.justOrEmpty(Optional.of(2))));
}
Optional<Mono<String>> testOptionalMapMonoJust() {
return Optional.of("foo").map(Mono::just);
}
Mono<Integer> testMonoFromOptionalSwitchIfEmpty() {
return Mono.justOrEmpty(Optional.of(1)).switchIfEmpty(Mono.just(2));
}
Mono<Tuple2<String, Integer>> testMonoZip() {
return Mono.zip(Mono.just("foo"), Mono.just(1));
}
@@ -99,8 +120,8 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Flux.just("foo").defaultIfEmpty("bar"), Flux.just("baz").defaultIfEmpty("qux"));
}
Mono<Integer> testMonoSwitchIfEmptyOfEmptyPublisher() {
return Mono.just(1);
ImmutableSet<Mono<?>> testMonoIdentity() {
return ImmutableSet.of(Mono.just(1), Mono.<Void>empty());
}
ImmutableSet<Flux<Integer>> testFluxSwitchIfEmptyOfEmptyPublisher() {
@@ -199,6 +220,10 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
Mono.just("foo").flux(), Mono.just("bar").flux(), Mono.just("baz").flux());
}
Mono<Void> testMonoThen() {
return Mono.just("foo").then();
}
Mono<Optional<String>> testMonoCollectToOptional() {
return Mono.just("foo").flux().collect(toOptional());
}
@@ -302,6 +327,14 @@ final class ReactorRulesTest implements RefasterRuleCollectionTestCase {
return Flux.just(1).onErrorReturn(IllegalArgumentException.class, 2);
}
Flux<Integer> testFluxFilterSort() {
return Flux.just(1, 4, 3, 2).filter(i -> i % 2 == 0).sort();
}
Flux<Integer> testFluxFilterSortWithComparator() {
return Flux.just(1, 4, 3, 2).filter(i -> i % 2 == 0).sort(reverseOrder());
}
ImmutableSet<Context> testContextEmpty() {
return ImmutableSet.of(Context.empty(), Context.empty());
}

View File

@@ -16,7 +16,7 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class StreamRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Streams.class, not(null), reverseOrder());
return ImmutableSet.of(Objects.class, Streams.class, not(null));
}
String testJoining() {
@@ -56,6 +56,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of("foo").flatMap(v -> Stream.of(v.length()).flatMap(Stream::of));
}
Stream<Integer> testStreamFilterSorted() {
return Stream.of(1, 4, 3, 2).sorted().filter(i -> i % 2 == 0);
}
Stream<Integer> testStreamFilterSortedWithComparator() {
return Stream.of(1, 4, 3, 2).sorted(reverseOrder()).filter(i -> i % 2 == 0);
}
ImmutableSet<Optional<Integer>> testStreamMapFirst() {
return ImmutableSet.of(
Stream.of("foo").map(s -> s.length()).findFirst(),

View File

@@ -18,7 +18,7 @@ import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class StreamRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Streams.class, not(null), reverseOrder());
return ImmutableSet.of(Objects.class, Streams.class, not(null));
}
String testJoining() {
@@ -57,6 +57,14 @@ final class StreamRulesTest implements RefasterRuleCollectionTestCase {
return Stream.of("foo").flatMap(v -> Stream.of(v.length())).flatMap(Stream::of);
}
Stream<Integer> testStreamFilterSorted() {
return Stream.of(1, 4, 3, 2).filter(i -> i % 2 == 0).sorted();
}
Stream<Integer> testStreamFilterSortedWithComparator() {
return Stream.of(1, 4, 3, 2).filter(i -> i % 2 == 0).sorted(reverseOrder());
}
ImmutableSet<Optional<Integer>> testStreamMapFirst() {
return ImmutableSet.of(
Stream.of("foo").findFirst().map(s -> s.length()),

108
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.5.1-SNAPSHOT</version>
<version>0.7.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -149,15 +149,15 @@
<version.auto-value>1.10.1</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.16</version.error-prone-orig>
<version.error-prone-slf4j>0.1.16</version.error-prone-slf4j>
<version.error-prone-orig>2.17.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.17</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.9.0</version.mockito>
<version.mockito>4.11.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.10.5</version.nullaway>
<version.pitest-git>1.0.1</version.pitest-git>
<version.nullaway>0.10.7</version.nullaway>
<version.pitest-git>1.0.3</version.pitest-git>
<version.surefire>2.22.2</version.surefire>
</properties>
@@ -281,7 +281,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2022.0.0</version>
<version>2022.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -333,17 +333,19 @@
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<artifactId>assertj-bom</artifactId>
<version>3.24.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.27.0</version>
<version>3.29.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -353,12 +355,12 @@
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<version>2.9.2</version>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
@@ -377,7 +379,7 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.5</version>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
@@ -389,7 +391,7 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.7.6</version>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -611,15 +613,15 @@
</property>
<property name="illegalClasses" value="com.mongodb.lang.Nullable">
<!-- Instead, please use
`org.jspecify.nullness.Nullable`. -->
`org.jspecify.annotations.Nullable`. -->
</property>
<property name="illegalClasses" value="io.micrometer.core.lang.Nullable">
<!-- Instead, please use
`org.jspecify.nullness.Nullable`. -->
`org.jspecify.annotations.Nullable`. -->
</property>
<property name="illegalClasses" value="javax.annotation.Nullable">
<!-- Instead, please use
`org.jspecify.nullness.Nullable`. -->
`org.jspecify.annotations.Nullable`. -->
</property>
<property name="illegalClasses" value="javax.annotation.concurrent.Immutable">
<!-- Instead, please use
@@ -635,7 +637,7 @@
</property>
<property name="illegalClasses" value="org.springframework.lang.Nullable">
<!-- Instead, please use
`org.jspecify.nullness.Nullable`. -->
`org.jspecify.annotations.Nullable`. -->
</property>
<property name="illegalPkgs" value="com.amazonaws.annotation" />
<property name="illegalPkgs" value="com.beust.jcommander.internal" />
@@ -686,10 +688,6 @@
<!-- Instead, please use
`com.google.common.collect.Streams`. -->
</property>
<property name="illegalClasses" value="org\.junit\.jupiter\.api\.Assertions(\..*?)?">
<!-- Instead, please use
`org.assertj.core.api.Assertions`. -->
</property>
<property name="illegalClasses" value="org\.springframework\.stereotype\.(Component|Controller|Service)">
<!-- We don't use Spring's
component scanning, so `@Component`
@@ -771,7 +769,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.5.0</version>
<version>10.6.0</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
@@ -812,6 +810,11 @@
<artifactId>error_prone_core</artifactId>
<version>${version.error-prone}</version>
</path>
<path>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_docgen_processor</artifactId>
<version>${version.error-prone}</version>
</path>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
@@ -852,6 +855,7 @@
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-Xmaxerrs</arg>
@@ -997,6 +1001,7 @@
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</additionalJOption>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</additionalJOption>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</additionalJOption>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</additionalJOption>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</additionalJOption>
<additionalJOption>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</additionalJOption>
</additionalJOptions>
@@ -1089,6 +1094,37 @@
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<mainClass>com.google.errorprone.DocGenTool</mainClass>
<arguments>
<argument>-bug_patterns=${project.build.directory}/generated-sources/annotations/bugPatterns.txt</argument>
<argument>-docs_repository=${project.build.directory}/generated-wiki/</argument>
<argument>-explanations=${basedir}/src/main/docs/bugpattern/</argument>
<argument>-target=external</argument>
</arguments>
<includePluginDependencies>true</includePluginDependencies>
</configuration>
<dependencies>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_docgen</artifactId>
<version>${version.error-prone}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>generate-bugpattern-docs</id>
<goals>
<goal>java</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
@@ -1195,7 +1231,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.13.0</version>
<version>2.14.2</version>
<configuration>
<updateBuildOutputTimestampPolicy>never</updateBuildOutputTimestampPolicy>
</configuration>
@@ -1239,7 +1275,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.9.11</version>
<version>1.10.4</version>
<configuration>
<excludedClasses>
<!-- AutoValue generated classes. -->
@@ -1248,6 +1284,10 @@
<excludedClass>*.refaster*.*Rules*</excludedClass>
</excludedClasses>
<failWhenNoMutations>false</failWhenNoMutations>
<mutators>
<mutator>EXTENDED</mutator>
<mutator>STRONGER</mutator>
</mutators>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
contention. -->
@@ -1261,10 +1301,20 @@
<artifactId>pitest-git-plugin</artifactId>
<version>${version.pitest-git}</version>
</dependency>
<dependency>
<groupId>com.groupcdg.arcmutate</groupId>
<artifactId>base</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.groupcdg.pitest</groupId>
<artifactId>pitest-accelerator-junit5</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>1.1.0</version>
<version>1.1.1</version>
</dependency>
</dependencies>
<executions>
@@ -1578,6 +1628,10 @@
-XepOpt:NullAway:AssertsEnabled=true
-XepOpt:NullAway:CheckOptionalEmptiness=true
-XepOpt:Nullness:Conservative=false
<!-- XXX: Enable once this check respects
the compilation source version. See
https://github.com/google/error-prone/pull/3646.
-XepOpt:StatementSwitchToExpressionSwitch:EnableDirectConversion=true -->
<!-- Append additional custom arguments. -->
${error-prone.patch-args}
${error-prone.self-check-args}

View File

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

View File

@@ -31,7 +31,7 @@ import java.util.Map;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.StandardLocation;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.AnnotatedCompositeCodeTransformer;
/**

View File

@@ -3,5 +3,5 @@
* files on the classpath.
*/
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster.plugin;

View File

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

View File

@@ -0,0 +1,16 @@
Error Prone's out-of-the-box support for the application of
[Refaster][refaster] templates is somewhat cumbersome. Additionally, by default
the focus of Refaster templates is on one-off code refactorings.
This plugin attempts to bring Refaster templates on equal footing with other
Error Prone plugins by locating all Refaster templates on the classpath and
reporing any match. The suggested changes can be applied using Error Prone's
built-in [patch][patching] functionality.
XXX: Expand documentation. Mention:
- The `refaster-resource-compiler`.
- How checks can be restricted using the `NamePattern` flag (see the Javadoc)
- An concrete patching example, with and without `NamePattern`.
[refaster]: https://errorprone.info/docs/refaster
[patching]: https://errorprone.info/docs/patching

View File

@@ -1,4 +1,4 @@
/** Exposes Refaster rules found on the classpath through a regular Error Prone check. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster.runner;

View File

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

View File

@@ -4,5 +4,5 @@
* non-patch mode.
*/
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster.annotation;

View File

@@ -5,5 +5,5 @@
* com.google.errorprone.refaster.annotation.NotMatches @NotMatches} annotations.
*/
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster.matchers;

View File

@@ -1,4 +1,4 @@
/** Assorted classes that aid the compilation or evaluation of Refaster rules. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster;

View File

@@ -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 org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
/**
* An abstract {@link BugChecker} that reports a match for each expression matched by the given

View File

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

View File

@@ -44,7 +44,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.refaster.runner.CodeTransformers;
import tech.picnic.errorprone.refaster.runner.Refaster;

View File

@@ -1,4 +1,4 @@
/** Opinionated utilities for the testing of Refaster rules. */
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.nullness.NullMarked
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.refaster.test;

View File

@@ -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 org.jspecify.nullness.Nullable;
import org.jspecify.annotations.Nullable;
/** Refaster rule collection to validate that having no violations works as expected. */
final class ValidRules {

View File

@@ -7,7 +7,7 @@
set -e -u -o pipefail
if [ "${#}" -gt 1 ]; then
echo "Usage: ./$(basename "${0}") [TargetTests]"
echo "Usage: ${0} [TargetTests]"
exit 1
fi

View File

@@ -24,6 +24,9 @@ If you are not familiar with Jekyll, be sure to check out its
[documentation][jekyll-docs]. It is recommended to follow the provided
step-by-step tutorial.
We use the [Just the Docs][just-the-docs] Jekyll theme, which also includes
several configuration options.
###### Switch Ruby versions
The required Ruby version is set in `.ruby-version`. To switch, you can use
@@ -56,5 +59,6 @@ Actions workflow any time a change is merged to `master`.
[jekyll]: https://jekyllrb.com
[jekyll-docs]: https://jekyllrb.com/docs
[jekyll-docs-installation]: https://jekyllrb.com/docs/installation
[just-the-docs]: https://just-the-docs.github.io/just-the-docs/
[localhost-port-4000]: http://127.0.0.1:4000
[rvm]: https://rvm.io

View File

@@ -28,6 +28,12 @@ nav_external_links:
url: https://github.com/PicnicSupermarket/error-prone-support
hide_icon: false
callouts:
summary:
color: blue
note:
color: grey-dk
# SEO configuration.
# See https://jekyll.github.io/jekyll-seo-tag/usage.
social:

View File

@@ -1,5 +1,5 @@
<img src="/assets/images/picnic-logo@2x.png" alt="Picnic Logo" id="logo" />
<img src="/assets/images/picnic-logo@2x.png" alt="Picnic Logo" id="logo"/>
<p align="center">
Copyright &copy; 2017-2022 Picnic Technologies BV
<p>
Copyright &copy; 2017-2023 Picnic Technologies BV
</p>

View File

@@ -4,15 +4,15 @@
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/favicon-16x16.png">
<link rel="manifest" href="/assets/images/site.webmanifest">
<link rel="mask-icon" href="/assets/images/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/assets/images/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<!-- XXX: The theme does not natively support both light and dark mode. Drop
this section once https://github.com/just-the-docs/just-the-docs/issues/234 is
resolved. -->
<!-- Support light and dark mode, as it's not natively supported. See
https://github.com/just-the-docs/just-the-docs/issues/234. -->
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-eps-light.css' | relative_url }}"
media="(prefers-color-scheme: light)">
media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="{{ '/assets/css/just-the-docs-eps-dark.css' | relative_url }}"
media="(prefers-color-scheme: dark)">
media="(prefers-color-scheme: dark)">
<meta name="google-site-verification" content="2GBzy2ufS8Rfqffu8T6iqng6dbDw9EKuykMisUZU3IQ"/>

View File

@@ -1,5 +1,20 @@
footer > img#logo {
// Add support for external anchor icons.
.external > svg {
width: 1rem;
vertical-align: text-bottom;
}
.label {
// Reduce spacing between labels and align with surrounding elements.
margin-left: 0 !important;
}
footer {
text-align: center;
img#logo {
width: 2rem;
margin: 0 auto;
display: block;
}
}

View File

@@ -1 +1,5 @@
// Overrides for Just the Docs. See
// https://github.com/just-the-docs/just-the-docs/blob/main/_sass/support/_variables.scss.
// Grid system.
$nav-width: 400px;

View File

@@ -1,3 +1,16 @@
@import "./color_schemes/dark";
@import "_variables";
@import "_common";
// Swap `$blue-000` and `$blue-300`, mainly for callouts. This is done by
// default for red, but not for other colors.
$blue-000: #183385;
$blue-300: #2c84fa;
// Use light-theme greys in dark theme so that summary callouts stand out more.
// (Note that the former has four shades, while the latter has five.)
$grey-dk-000: $grey-lt-000;
$grey-dk-100: $grey-lt-100;
$grey-dk-200: $grey-lt-200;
$grey-dk-250: $grey-lt-200;
$grey-dk-300: $grey-lt-300;