Compare commits

..

15 Commits

Author SHA1 Message Date
Rick Ossendrijver
0d98178fc8 Add a first version of the check for incorrect method signature 2021-03-22 16:51:00 +01:00
Stephan Schroevers
a1b92ef92a Suggestions 2021-02-27 12:47:44 +01:00
Rick Ossendrijver
857e912061 Change severity and tag 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
f87b916329 Remove trailing comments 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
3b15c3bf7d Improve the method by using getKind and ::cast 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
a3c828fe94 Format and turn back on refaster checks 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
0cbcdb8c8b Refactor names and the test class names 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
7548278b91 Remove wildcart import 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
5b2a391523 Remove unused imports 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
6f98868171 Add test cases and improve the comments 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
0ad09268e0 Make the description work and fix the test 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
7f246785cf Update some comments 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
7dab0eb562 Add comment for the next time 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
fa004e74b4 Add retrieval of methods and try some multimatcher things 2021-02-27 12:32:05 +01:00
Rick Ossendrijver
e9fcfc7624 Add file and test for missing refaster annotations 2021-02-27 12:32:05 +01:00
267 changed files with 3351 additions and 12248 deletions

View File

@@ -1,37 +0,0 @@
name: Build and verify
on:
pull_request:
push:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
jdk: [ 11.0.16, 17.0.4 ]
steps:
# We run the build twice for each supported JDK: once against the
# original Error Prone release, using only Error Prone checks available
# on Maven Central, and once against the Picnic Error Prone fork,
# additionally enabling all checks defined in this project and any
# Error Prone checks available only from other artifact repositories.
- name: Check out code
uses: actions/checkout@v3.0.2
- name: Set up JDK
uses: actions/setup-java@v3.4.1
with:
java-version: ${{ matrix.jdk }}
distribution: temurin
cache: maven
- name: Display build environment details
run: mvn --version
- name: Build project against vanilla Error Prone
run: mvn -T1C install
- name: Build project with self-check against Error Prone fork
run: mvn -T1C clean verify -Perror-prone-fork -Pnon-maven-central -Pself-check -s settings.xml
- name: Remove installed project artifacts
run: mvn build-helper:remove-project-artifact
# XXX: Enable Codecov once we "go public".
# XXX: Enable SonarCloud once we "go public".

View File

@@ -1,14 +0,0 @@
-XX:ReservedCodeCacheSize=512m
-XX:SoftRefLRUPolicyMSPerMB=10
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED

View File

@@ -1 +0,0 @@
--batch-mode --errors --strict-checksums

View File

@@ -1,22 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"matchPackagePatterns": [
"^org\\.springframework:spring-framework-bom$",
"^org\\.springframework\\.boot:spring-boot[a-z-]*$"
],
"separateMinorPatch": true
},
{
"matchPackagePatterns": [
"^com\\.palantir\\.baseline:baseline-error-prone$"
],
"schedule": "* * 1 * *"
}
],
"reviewers": [
"rickie",
"Stephan202"
]
}

37
.travis.yml Normal file
View File

@@ -0,0 +1,37 @@
---
dist: bionic
language: java
jdk: openjdk11
addons:
sonarcloud:
organization: picnic-technologies
token: "${SONARCLOUD_TOKEN}"
install:
- mvn io.takari:maven:wrapper
script:
# We run the build twice: once against the original Error Prone release,
# using only Error Prone checks available on Maven Central, and once against
# the Picnic Error Prone fork, additionally enabling Error Prone checks
# available from other artifact repositories.
- ./mvnw clean install
- ./mvnw clean install -Perror-prone-fork -Pnon-maven-central -s settings.xml
# XXX: Enable SonarCloud once we "go public".
# ./mvnw jacoco:prepare-agent surefire:test jacoco:report sonar:sonar
- ./mvnw jacoco:prepare-agent surefire:test jacoco:report
before_cache:
# Don't cache the artifacts we just generated, for multiple reasons: (1) we
# shouldn't need them next time around and (2) if we do, that indicates a
# dependency issue which might otherwise go unnoticed until next time we bump
# the project's version (i.e., when tagging).
- find "${HOME}/.m2/repository" -depth -name '*-SNAPSHOT' -exec rm -r '{}' \;
cache:
directories:
# The local Maven repository in which third party dependencies are stored.
- ${HOME}/.m2/repository
# The Takari Maven Wrapper's storage for downloaded Maven distributions.
- ${HOME}/.m2/wrapper
# The SonarQube analysis cache.
- ${HOME}/.sonar/cache
# XXX: Enable Codecov once we "go public".
#after_success:
# - bash <(curl -s https://codecov.io/bash)

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env bash
# Compiles the code using Error Prone and applies its suggestions. The set of
# checks applied can optionally be restricted by name.
#
# As this script may modify the project's code, it is important to execute it
# in a clean Git working directory.
set -e -u -o pipefail
if [ "${#}" -gt 1 ]; then
echo "Usage: ./$(basename "${0}") [PatchChecks]"
exit 1
fi
patchChecks=${1:-}
mvn clean test-compile fmt:format \
-T 1.0C \
-Perror-prone \
-Perror-prone-fork \
-Ppatch \
-Pself-check \
-Derror-prone.patch-checks="${patchChecks}" \
-Dverification.skip

View File

@@ -276,6 +276,10 @@ Refaster's expressiveness:
to be lost. In such a case don't statically import the method, so that the
generic type information can be retained. (There may be cases where generic
type information should even be _added_. Find an example.)
- Upon application of a template Refaster can throw a _No binding for
Key{identifier=someAfterTemplateParam}_ exception. When this happens the
template is invalid. Instead perform this check at compile time, such that
such malformed templates cannot be defined in the first place.
- Provide a way to express "match if (not) annotated (with _X_)". See #1 for a
motivating example.
- Provide a way to place match constraints on compile time constants. For

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -41,37 +41,22 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<artifactId>refaster-resource-compiler</artifactId>
<!-- This dependency is declared only as a hint to Maven that
compilation depends on it; see the `maven-compiler-plugin`'s
`annotationProcessorPaths` configuration below. -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
<scope>provided</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
@@ -82,20 +67,11 @@
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.newrelic.agent.java</groupId>
<artifactId>newrelic-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
@@ -136,11 +112,6 @@
<artifactId>jaxb-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
@@ -152,14 +123,8 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<scope>runtime</scope>
<!-- XXX: Consider making optional? Ship only the definitions? -->
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -194,23 +159,18 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>provided</scope>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>provided</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
@@ -222,6 +182,15 @@
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.coveo</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<configuration>
<additionalSourceDirectories>
<additionalSourceDirectory>${basedir}/src/test/resources</additionalSourceDirectory>
</additionalSourceDirectories>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@@ -229,31 +198,15 @@
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
<artifactId>refaster-resource-compiler</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleCompiler</arg>
<arg>-Xplugin:RefasterRuleResourceCompiler</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
<ignoredUnusedDeclaredDependencies>
<!-- XXX: Figure out why the plugin thinks this
dependency is unused. -->
<ignoredUnusedDeclaredDependency>${project.groupId}:refaster-support</ignoredUnusedDeclaredDependency>
</ignoredUnusedDeclaredDependencies>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

View File

@@ -1,67 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
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.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import java.util.Map;
import javax.lang.model.element.AnnotationValue;
/** A {@link BugChecker} which flags ambiguous {@code @JsonCreator}s in enums. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "`JsonCreator.Mode` should be set for single-argument creators",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class AmbiguousJsonCreator extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<AnnotationTree> IS_JSON_CREATOR_ANNOTATION =
isType("com.fasterxml.jackson.annotation.JsonCreator");
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
if (!IS_JSON_CREATOR_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
ClassTree clazz = state.findEnclosing(ClassTree.class);
if (clazz == null || clazz.getKind() != Tree.Kind.ENUM) {
return Description.NO_MATCH;
}
MethodTree method = state.findEnclosing(MethodTree.class);
if (method == null || method.getParameters().size() != 1) {
return Description.NO_MATCH;
}
boolean customMode =
ASTHelpers.getAnnotationMirror(tree).getElementValues().entrySet().stream()
.filter(entry -> entry.getKey().getSimpleName().contentEquals("mode"))
.map(Map.Entry::getValue)
.map(AnnotationValue::getValue)
.filter(Symbol.VarSymbol.class::isInstance)
.map(Symbol.VarSymbol.class::cast)
.anyMatch(varSymbol -> !varSymbol.getSimpleName().contentEquals("DEFAULT"));
return customMode
? Description.NO_MATCH
: describeMatch(
tree, SuggestedFix.replace(tree, "@JsonCreator(mode = JsonCreator.Mode.DELEGATING)"));
}
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns.util;
package tech.picnic.errorprone.bugpatterns;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
@@ -28,7 +28,7 @@ import java.util.stream.Stream;
* <p>This class allows one to define a whitelist or blacklist of annotations or their attributes.
* Annotations are identified by their fully qualified name.
*/
public final class AnnotationAttributeMatcher implements Serializable {
final class AnnotationAttributeMatcher implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean complement;
@@ -59,7 +59,7 @@ public final class AnnotationAttributeMatcher implements Serializable {
* @param exclusions The listed annotations or annotation attributes are not matched.
* @return A non-{@code null} {@link AnnotationAttributeMatcher}.
*/
public static AnnotationAttributeMatcher create(
static AnnotationAttributeMatcher create(
Optional<? extends List<String>> inclusions, Iterable<String> exclusions) {
Set<String> includedWholeTypes = new HashSet<>();
Set<String> excludedWholeTypes = new HashSet<>();
@@ -97,13 +97,7 @@ public final class AnnotationAttributeMatcher implements Serializable {
}
}
/**
* Returns the subset of arguments of the given {@link AnnotationTree} matched by this instance.
*
* @param tree The annotation AST node to be inspected.
* @return Any matching annotation arguments.
*/
public Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
Stream<? extends ExpressionTree> extractMatchingArguments(AnnotationTree tree) {
Type type = ASTHelpers.getType(tree.getAnnotationType());
if (type == null) {
return Stream.empty();

View File

@@ -1,57 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.nullLiteral;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
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.sun.source.tree.MethodInvocationTree;
/**
* A {@link BugChecker} which flags AssertJ {@code isEqualTo(null)} checks for simplification.
*
* <p>This bug checker cannot be replaced with a simple Refaster template, as the Refaster approach
* would require that all overloads of {@link org.assertj.core.api.Assert#isEqualTo(Object)} (such
* as {@link org.assertj.core.api.AbstractStringAssert#isEqualTo(String)}) are explicitly
* enumerated. This bug checker generically matches all such current and future overloads.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `.isNull()` over `.isEqualTo(null)`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AssertJIsNull extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> ASSERT_IS_EQUAL_TO_NULL =
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.Assert").named("isEqualTo"),
argumentCount(1),
argument(0, nullLiteral()));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!ASSERT_IS_EQUAL_TO_NULL.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fix =
SuggestedFix.builder().merge(SuggestedFixes.renameMethodInvocation(tree, "isNull", state));
tree.getArguments().forEach(arg -> fix.merge(SuggestedFix.delete(arg)));
return describeMatch(tree, fix.build());
}
}

View File

@@ -1,16 +1,15 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
@@ -27,11 +26,12 @@ import java.util.List;
/** A {@link BugChecker} which flags redundant {@code @Autowired} constructor annotations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "AutowiredConstructor",
summary = "Omit `@Autowired` on a class' sole constructor, as it is redundant",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class AutowiredConstructor extends BugChecker implements ClassTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class AutowiredConstructorCheck extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> AUTOWIRED_ANNOTATION =
annotations(AT_LEAST_ONE, isType("org.springframework.beans.factory.annotation.Autowired"));
@@ -43,7 +43,7 @@ public final class AutowiredConstructor extends BugChecker implements ClassTreeM
return Description.NO_MATCH;
}
ImmutableList<AnnotationTree> annotations =
List<AnnotationTree> annotations =
AUTOWIRED_ANNOTATION
.multiMatchResult(Iterables.getOnlyElement(constructors), state)
.matchingNodes();

View File

@@ -1,12 +1,11 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
@@ -25,24 +24,25 @@ import java.util.Optional;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags annotations that could be written more concisely. */
@AutoService(BugChecker.class)
@BugPattern(
name = "CanonicalAnnotationSyntax",
summary = "Omit redundant syntax from annotation declarations",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class CanonicalAnnotationSyntaxCheck extends BugChecker
implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
FIX_FACTORIES =
ImmutableSet.of(
CanonicalAnnotationSyntax::dropRedundantParentheses,
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
CanonicalAnnotationSyntax::dropRedundantCurlies);
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
@@ -102,8 +102,7 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
return Optional.of(
SuggestedFix.replace(
arg,
simplifyAttributeValue(expr, state)
.orElseGet(() -> SourceCode.treeToString(expr, state))));
simplifyAttributeValue(expr, state).orElseGet(() -> Util.treeToString(expr, state))));
}
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
@@ -138,11 +137,11 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
private static Optional<String> simplifySingletonArray(NewArrayTree array, VisitorState state) {
return Optional.of(array.getInitializers())
.filter(initializers -> initializers.size() == 1)
.map(initializers -> SourceCode.treeToString(initializers.get(0), state));
.map(initializers -> Util.treeToString(initializers.get(0), state));
}
private static Optional<String> dropTrailingComma(NewArrayTree array, VisitorState state) {
String src = SourceCode.treeToString(array, state);
String src = Util.treeToString(array, state);
return Optional.of(TRAILING_ARRAY_COMMA.matcher(src))
.filter(Matcher::find)
.map(m -> src.substring(0, m.start()) + '}');

View File

@@ -1,117 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
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.staticMethod;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
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.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.stream.Collector;
/**
* A {@link BugChecker} which flags {@link Collector Collectors} that don't clearly express
* (im)mutability.
*
* <p>Replacing such collectors with alternatives that produce immutable collections is preferred.
* Do note that Guava's immutable collections are null-hostile.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Avoid `Collectors.to{List,Map,Set}` in favour of alternatives that emphasize (im)mutability",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class CollectorMutability extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> COLLECTOR_METHOD =
staticMethod().onClass("java.util.stream.Collectors");
private static final Matcher<ExpressionTree> LIST_COLLECTOR =
staticMethod().anyClass().named("toList");
private static final Matcher<ExpressionTree> MAP_COLLECTOR =
staticMethod().anyClass().named("toMap");
private static final Matcher<ExpressionTree> SET_COLLECTOR =
staticMethod().anyClass().named("toSet");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!COLLECTOR_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
if (LIST_COLLECTOR.matches(tree, state)) {
return suggestToCollectionAlternatives(
tree, "com.google.common.collect.ImmutableList.toImmutableList", "ArrayList", state);
}
if (MAP_COLLECTOR.matches(tree, state)) {
return suggestToMapAlternatives(tree, state);
}
if (SET_COLLECTOR.matches(tree, state)) {
return suggestToCollectionAlternatives(
tree, "com.google.common.collect.ImmutableSet.toImmutableSet", "HashSet", state);
}
return Description.NO_MATCH;
}
private Description suggestToCollectionAlternatives(
MethodInvocationTree tree,
String fullyQualifiedImmutableReplacement,
String mutableReplacement,
VisitorState state) {
SuggestedFix.Builder mutableFix = SuggestedFix.builder();
String toCollectionSelect =
SuggestedFixes.qualifyStaticImport(
"java.util.stream.Collectors.toCollection", mutableFix, state);
return buildDescription(tree)
.addFix(replaceMethodInvocation(tree, fullyQualifiedImmutableReplacement, state))
.addFix(
mutableFix
.addImport(String.format("java.util.%s", mutableReplacement))
.replace(tree, String.format("%s(%s::new)", toCollectionSelect, mutableReplacement))
.build())
.build();
}
private Description suggestToMapAlternatives(MethodInvocationTree tree, VisitorState state) {
int argCount = tree.getArguments().size();
if (argCount > 3) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(
replaceMethodInvocation(
tree, "com.google.common.collect.ImmutableMap.toImmutableMap", state))
.addFix(
SuggestedFix.builder()
.addImport("java.util.HashMap")
.postfixWith(
tree.getArguments().get(argCount - 1),
(argCount == 2 ? ", (a, b) -> { throw new IllegalStateException(); }" : "")
+ ", HashMap::new")
.build())
.build();
}
private static SuggestedFix replaceMethodInvocation(
MethodInvocationTree tree, String fullyQualifiedReplacement, VisitorState state) {
SuggestedFix.Builder fix = SuggestedFix.builder();
String replacement = SuggestedFixes.qualifyStaticImport(fullyQualifiedReplacement, fix, state);
fix.merge(SuggestedFix.replace(tree.getMethodSelect(), replacement));
return fix.build();
}
}

View File

@@ -1,8 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
@@ -10,6 +7,9 @@ import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
@@ -17,21 +17,21 @@ 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.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Optional;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
/** A {@link BugChecker} which flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@BugPattern(
name = "EmptyMethod",
summary = "Empty method can likely be deleted",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<Tree> PERMITTED_ANNOTATION =
private static final Matcher<Tree> HAS_PERMITTED_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(isType("java.lang.Override"), isType("org.aspectj.lang.annotation.Pointcut")));
@@ -41,22 +41,15 @@ public final class EmptyMethod extends BugChecker implements MethodTreeMatcher {
if (tree.getBody() == null
|| !tree.getBody().getStatements().isEmpty()
|| ASTHelpers.containsComments(tree, state)
|| PERMITTED_ANNOTATION.matches(tree, state)
|| isInPossibleTestHelperClass(state)) {
|| HAS_PERMITTED_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
if (ASTHelpers.methodCanBeOverridden(ASTHelpers.getSymbol(tree))) {
MethodSymbol sym = ASTHelpers.getSymbol(tree);
if (sym == null || ASTHelpers.methodCanBeOverridden(sym)) {
return Description.NO_MATCH;
}
return describeMatch(tree, SuggestedFix.delete(tree));
}
private static boolean isInPossibleTestHelperClass(VisitorState state) {
return Optional.ofNullable(ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class))
.map(ClassTree::getSimpleName)
.filter(name -> name.toString().contains("Test"))
.isPresent();
}
}

View File

@@ -1,160 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static java.util.stream.Collectors.joining;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.googlejavaformat.java.Formatter;
import com.google.googlejavaformat.java.FormatterException;
import com.google.googlejavaformat.java.ImportOrderer;
import com.google.googlejavaformat.java.JavaFormatterOptions.Style;
import com.google.googlejavaformat.java.RemoveUnusedImports;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.util.Position;
import java.util.List;
import java.util.Optional;
/**
* A {@link BugChecker} which flags improperly formatted Error Prone test code.
*
* <p>All test code should be formatted in accordance with Google Java Format's {@link Formatter}
* output, and imports should be ordered according to the {@link Style#GOOGLE Google} style.
*
* <p>This checker inspects inline code passed to {@code
* com.google.errorprone.CompilationTestHelper} and {@code
* com.google.errorprone.BugCheckerRefactoringTestHelper}. It requires that this code is properly
* formatted and that its imports are organized. Only code that represents the expected output of a
* refactoring operation is allowed to have unused imports, as most {@link BugChecker}s do not (and
* are not able to) remove imports that become obsolete as a result of applying their suggested
* fix(es).
*/
// XXX: Once we target JDK 17 (optionally?) suggest text block fixes.
// XXX: GJF guesses the line separator to be used by inspecting the source. When using text blocks
// this may cause the current unconditional use of `\n` not to be sufficient when building on
// Windows; TBD.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Test code should follow the Google Java style",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class ErrorProneTestHelperSourceFormat extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Formatter FORMATTER = new Formatter();
private static final Matcher<ExpressionTree> INPUT_SOURCE_ACCEPTING_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines"),
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines"));
private static final Matcher<ExpressionTree> OUTPUT_SOURCE_ACCEPTING_METHOD =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
boolean isOutputSource = OUTPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state);
if (!isOutputSource && !INPUT_SOURCE_ACCEPTING_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
if (sourceLines.isEmpty()) {
return buildDescription(tree).setMessage("No source code provided").build();
}
int startPos = ASTHelpers.getStartPosition(sourceLines.get(0));
int endPos = state.getEndPosition(sourceLines.get(sourceLines.size() - 1));
/* Attempt to format the source code only if it fully consists of constant expressions. */
return getConstantSourceCode(sourceLines)
.map(source -> flagFormattingIssues(startPos, endPos, source, isOutputSource, state))
.orElse(Description.NO_MATCH);
}
private Description flagFormattingIssues(
int startPos, int endPos, String source, boolean retainUnusedImports, VisitorState state) {
Tree methodInvocation = state.getPath().getLeaf();
String formatted;
try {
formatted = formatSourceCode(source, retainUnusedImports).trim();
} catch (FormatterException e) {
return buildDescription(methodInvocation)
.setMessage(String.format("Source code is malformed: %s", e.getMessage()))
.build();
}
if (source.trim().equals(formatted)) {
return Description.NO_MATCH;
}
if (startPos == Position.NOPOS || endPos == Position.NOPOS) {
/*
* We have insufficient source information to emit a fix, so we only flag the fact that the
* code isn't properly formatted.
*/
return describeMatch(methodInvocation);
}
/*
* The code isn't properly formatted; replace all lines with the properly formatted
* alternatives.
*/
return describeMatch(
methodInvocation,
SuggestedFix.replace(
startPos,
endPos,
Splitter.on('\n')
.splitToStream(formatted)
.map(state::getConstantExpression)
.collect(joining(", "))));
}
private static String formatSourceCode(String source, boolean retainUnusedImports)
throws FormatterException {
String withReorderedImports = ImportOrderer.reorderImports(source, Style.GOOGLE);
String withOptionallyRemovedImports =
retainUnusedImports
? withReorderedImports
: RemoveUnusedImports.removeUnusedImports(withReorderedImports);
return FORMATTER.formatSource(withOptionallyRemovedImports);
}
private static Optional<String> getConstantSourceCode(
List<? extends ExpressionTree> sourceLines) {
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return Optional.empty();
}
source.append(value).append('\n');
}
return Optional.of(source.toString());
}
}

View File

@@ -1,89 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.errorprone.BugPattern.LinkType.NONE;
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.staticMethod;
import static java.util.stream.Collectors.collectingAndThen;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
/**
* A {@link BugChecker} which flags {@link Ordering#explicit(Object, Object[])}} invocations listing
* a subset of an enum type's values.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Make sure `Ordering#explicit` lists all of an enum's values",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class ExplicitEnumOrdering extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> EXPLICIT_ORDERING =
staticMethod().onClass(Ordering.class.getName()).named("explicit");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!EXPLICIT_ORDERING.matches(tree, state)) {
return Description.NO_MATCH;
}
ImmutableSet<String> missingEnumValues = getMissingEnumValues(tree.getArguments());
if (missingEnumValues.isEmpty()) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
String.format(
"Explicit ordering lacks some enum values: %s",
String.join(", ", missingEnumValues)))
.build();
}
private static ImmutableSet<String> getMissingEnumValues(
List<? extends ExpressionTree> expressions) {
return expressions.stream()
.map(ASTHelpers::getSymbol)
.filter(Symbol::isEnum)
.collect(
collectingAndThen(
toImmutableSetMultimap(Symbol::asType, Symbol::toString),
ExplicitEnumOrdering::getMissingEnumValues));
}
private static ImmutableSet<String> getMissingEnumValues(
ImmutableSetMultimap<Type, String> valuesByType) {
return Multimaps.asMap(valuesByType).entrySet().stream()
.flatMap(e -> getMissingEnumValues(e.getKey(), e.getValue()))
.collect(toImmutableSet());
}
private static Stream<String> getMissingEnumValues(Type enumType, Set<String> values) {
Symbol.TypeSymbol typeSymbol = enumType.asElement();
return Sets.difference(ASTHelpers.enumValues(typeSymbol), values).stream()
.map(v -> String.format("%s.%s", typeSymbol.getSimpleName(), v));
}
}

View File

@@ -1,85 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import 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.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
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.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
/**
* A {@link BugChecker} which flags usages of {@link Flux#flatMap(Function)} and {@link
* Flux#flatMapSequential(Function)}.
*
* <p>{@link Flux#flatMap(Function)} and {@link Flux#flatMapSequential(Function)} eagerly perform up
* to {@link reactor.util.concurrent.Queues#SMALL_BUFFER_SIZE} subscriptions. Additionally, the
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
* avoids potentially saturating the thread pool on which subscription happens. If {@code
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
* {@code flatMapSequential} overload with an explicit concurrency level.
*
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
* by this check because there is no clear alternative to point to.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"`Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; "
+ "please use `Flux#concatMap` or explicitly specify the desired amount of concurrency",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf("flatMap", "flatMapSequential")
.withParameters(Function.class.getName());
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!FLUX_FLATMAP.matches(tree, state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
.addFix(
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build())
.build();
}
@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
if (!FLUX_FLATMAP.matches(tree, state)) {
return Description.NO_MATCH;
}
// Method references are expected to occur very infrequently; generating both variants of
// suggested fixes is not worth the trouble.
return describeMatch(tree);
}
}

View File

@@ -1,257 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyMethod;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static java.util.stream.Collectors.joining;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
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.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.SimpleTreeVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags string concatenations that produce a format string; in such
* cases the string concatenation should instead be deferred to the invoked method.
*
* @implNote This checker is based on the implementation of {@link
* com.google.errorprone.bugpatterns.flogger.FloggerStringConcatenation}.
*/
// XXX: Support arbitrary `@FormatMethod`-annotated methods.
// XXX: For (explicit or delegated) invocations of `java.util.Formatter` _strictly speaking_ we
// should introduce special handling of `Formattable` arguments, as this check would replace a
// `Formattable#toString` invocation with a `Formattable#formatTo` invocation. But likely that
// should be considered a bug fix, too.
// XXX: Introduce a separate check which adds/removes the `Locale` parameter to `String.format`
// invocations, as necessary.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Defer string concatenation to the invoked method",
linkType = NONE,
severity = WARNING,
tags = SIMPLIFICATION)
public final class FormatStringConcatenation extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
/**
* AssertJ exposes varargs {@code fail} methods with a {@link Throwable}-accepting overload, the
* latter of which should not be flagged.
*/
private static final Matcher<ExpressionTree> ASSERTJ_FAIL_WITH_THROWABLE_METHOD =
anyMethod()
.anyClass()
.withAnyName()
.withParameters(String.class.getName(), Throwable.class.getName());
// XXX: Drop some of these methods if we use Refaster to replace some with others.
private static final Matcher<ExpressionTree> ASSERTJ_FORMAT_METHOD =
anyOf(
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractAssert")
.namedAnyOf("overridingErrorMessage", "withFailMessage"),
allOf(
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractSoftAssertions")
.named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)),
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractStringAssert")
.named("isEqualTo"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.AbstractThrowableAssert")
.namedAnyOf(
"hasMessage",
"hasMessageContaining",
"hasMessageEndingWith",
"hasMessageStartingWith",
"hasRootCauseMessage",
"hasStackTraceContaining"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.Descriptable")
.namedAnyOf("as", "describedAs"),
instanceMethod()
.onDescendantOf("org.assertj.core.api.ThrowableAssertAlternative")
.namedAnyOf(
"withMessage",
"withMessageContaining",
"withMessageEndingWith",
"withMessageStartingWith",
"withStackTraceContaining"),
allOf(
instanceMethod().onDescendantOf("org.assertj.core.api.WithAssertions").named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)),
allOf(
staticMethod()
.onClassAny(
"org.assertj.core.api.Assertions",
"org.assertj.core.api.BDDAssertions",
"org.assertj.core.api.Fail")
.named("fail"),
not(ASSERTJ_FAIL_WITH_THROWABLE_METHOD)));
private static final Matcher<ExpressionTree> GUAVA_FORMAT_METHOD =
anyOf(
staticMethod()
.onClass("com.google.common.base.Preconditions")
.namedAnyOf("checkArgument", "checkNotNull", "checkState"),
staticMethod().onClass("com.google.common.base.Verify").named("verify"));
// XXX: Add `PrintWriter`, maybe others.
private static final Matcher<ExpressionTree> JDK_FORMAT_METHOD =
anyOf(
staticMethod().onClass("java.lang.String").named("format"),
instanceMethod().onExactClass("java.util.Formatter").named("format"));
private static final Matcher<ExpressionTree> SLF4J_FORMAT_METHOD =
instanceMethod()
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("debug", "error", "info", "trace", "warn");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (hasNonConstantStringConcatenationArgument(tree, 0, state)) {
return flagViolation(tree, ASSERTJ_FORMAT_METHOD, 0, "%s", state)
.or(() -> flagViolation(tree, JDK_FORMAT_METHOD, 0, "%s", state))
.or(() -> flagViolation(tree, SLF4J_FORMAT_METHOD, 0, "{}", state))
.orElse(Description.NO_MATCH);
}
if (hasNonConstantStringConcatenationArgument(tree, 1, state)) {
return flagViolation(tree, GUAVA_FORMAT_METHOD, 1, "%s", state)
.or(() -> flagViolation(tree, JDK_FORMAT_METHOD, 1, "%s", state))
.or(() -> flagViolation(tree, SLF4J_FORMAT_METHOD, 1, "{}", state))
.orElse(Description.NO_MATCH);
}
return Description.NO_MATCH;
}
/**
* Flags the given method invocation if it matches a targeted method and passes a non-compile time
* constant string concatenation as a format string.
*/
private Optional<Description> flagViolation(
MethodInvocationTree tree,
Matcher<ExpressionTree> matcher,
int formatStringParam,
String formatSpecifier,
VisitorState state) {
if (!matcher.matches(tree, state)) {
/* The invoked method is not targeted by this check. */
return Optional.empty();
}
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() > formatStringParam + 1) {
/*
* This method invocation uses explicit string concatenation but _also_ already relies on
* format specifiers: flag but don't suggest a fix.
*/
return Optional.of(describeMatch(tree));
}
ExpressionTree formatStringArg = arguments.get(formatStringParam);
ReplacementArgumentsConstructor replacementConstructor =
new ReplacementArgumentsConstructor(formatSpecifier);
formatStringArg.accept(replacementConstructor, state);
return Optional.of(
describeMatch(
tree,
SuggestedFix.replace(
formatStringArg, replacementConstructor.getReplacementArguments(state))));
}
private static boolean hasNonConstantStringConcatenationArgument(
MethodInvocationTree tree, int argPosition, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() <= argPosition) {
/* This method doesn't accept enough parameters. */
return false;
}
ExpressionTree argument = ASTHelpers.stripParentheses(arguments.get(argPosition));
return argument instanceof BinaryTree
&& isStringTyped(argument, state)
&& ASTHelpers.constValue(argument, String.class) == null;
}
private static boolean isStringTyped(ExpressionTree tree, VisitorState state) {
return ASTHelpers.isSameType(ASTHelpers.getType(tree), state.getSymtab().stringType, state);
}
private static class ReplacementArgumentsConstructor
extends SimpleTreeVisitor<Void, VisitorState> {
private final StringBuilder formatString = new StringBuilder();
private final List<Tree> formatArguments = new ArrayList<>();
private final String formatSpecifier;
ReplacementArgumentsConstructor(String formatSpecifier) {
this.formatSpecifier = formatSpecifier;
}
@Nullable
@Override
public Void visitBinary(BinaryTree tree, VisitorState state) {
if (tree.getKind() == Kind.PLUS && isStringTyped(tree, state)) {
tree.getLeftOperand().accept(this, state);
tree.getRightOperand().accept(this, state);
} else {
appendExpression(tree);
}
return null;
}
@Nullable
@Override
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
return tree.getExpression().accept(this, state);
}
@Nullable
@Override
protected Void defaultAction(Tree tree, VisitorState state) {
appendExpression(tree);
return null;
}
private void appendExpression(Tree tree) {
if (tree instanceof LiteralTree) {
formatString.append(((LiteralTree) tree).getValue());
} else {
formatString.append(formatSpecifier);
formatArguments.add(tree);
}
}
private String getReplacementArguments(VisitorState state) {
return state.getConstantExpression(formatString.toString())
+ ", "
+ formatArguments.stream()
.map(tree -> SourceCode.treeToString(tree, state))
.collect(joining(", "));
}
}
}

View File

@@ -1,110 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.suppliers.Suppliers.OBJECT_TYPE;
import com.google.auto.service.AutoService;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.TypesWithUndefinedEquality;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ASTHelpers.TargetType;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import java.util.Arrays;
import java.util.List;
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
// the target method such a modification may change the code's semantics or performance.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid or clarify identity conversions",
linkType = NONE,
severity = WARNING,
tags = SIMPLIFICATION)
public final class IdentityConversion extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> IS_CONVERSION_METHOD =
anyOf(
staticMethod()
.onClassAny(
"com.google.common.collect.ImmutableBiMap",
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableListMultimap",
"com.google.common.collect.ImmutableMap",
"com.google.common.collect.ImmutableMultimap",
"com.google.common.collect.ImmutableMultiset",
"com.google.common.collect.ImmutableRangeMap",
"com.google.common.collect.ImmutableRangeSet",
"com.google.common.collect.ImmutableSet",
"com.google.common.collect.ImmutableSetMultimap",
"com.google.common.collect.ImmutableTable")
.named("copyOf"),
staticMethod()
.onClassAny(
Primitives.allWrapperTypes().stream()
.map(Class::getName)
.collect(toImmutableSet()))
.named("valueOf"),
staticMethod().onClass(String.class.getName()).named("valueOf"),
staticMethod().onClass("reactor.adapter.rxjava.RxJava2Adapter"),
staticMethod()
.onClass("reactor.core.publisher.Flux")
.namedAnyOf("concat", "firstWithSignal", "from", "merge"),
staticMethod().onClass("reactor.core.publisher.Mono").namedAnyOf("from", "fromDirect"));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.size() != 1 || !IS_CONVERSION_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree sourceTree = arguments.get(0);
Type sourceType = ASTHelpers.getType(sourceTree);
Type resultType = ASTHelpers.getType(tree);
TargetType targetType = ASTHelpers.targetType(state);
if (sourceType == null || resultType == null || targetType == null) {
return Description.NO_MATCH;
}
if (!state.getTypes().isSameType(sourceType, resultType)
&& !isConvertibleWithWellDefinedEquality(sourceType, targetType.type(), state)) {
return Description.NO_MATCH;
}
return buildDescription(tree)
.setMessage(
"This method invocation appears redundant; remove it or suppress this warning and "
+ "add a comment explaining its purpose")
.addFix(SuggestedFix.replace(tree, SourceCode.treeToString(sourceTree, state)))
.addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()))
.build();
}
private static boolean isConvertibleWithWellDefinedEquality(
Type sourceType, Type targetType, VisitorState state) {
Types types = state.getTypes();
return !types.isSameType(targetType, OBJECT_TYPE.get(state))
&& types.isConvertible(sourceType, targetType)
&& Arrays.stream(TypesWithUndefinedEquality.values())
.noneMatch(b -> b.matchesType(sourceType, state) || b.matchesType(targetType, state));
}
}

View File

@@ -1,81 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.methodReturns;
import static com.google.errorprone.matchers.Matchers.not;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.MethodTree;
import java.util.SortedSet;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} which flags {@link SortedSet} property declarations inside
* {@code @Value.Immutable}- and {@code @Value.Modifiable}-annotated types that lack a
* {@code @Value.NaturalOrder} or {@code @Value.ReverseOrder} annotation.
*
* <p>Without such an annotation:
*
* <ul>
* <li>deserialization of the enclosing type requires that the associated JSON property is
* present, contrary to the way in which Immutables handles other collection properties; and
* <li>different instances may use different comparator implementations (e.g. deserialization
* would default to natural order sorting), potentially leading to subtle bugs.
* </ul>
*/
@AutoService(BugChecker.class)
@BugPattern(
summary =
"`SortedSet` properties of a `@Value.Immutable` or `@Value.Modifiable` type must be "
+ "annotated with `@Value.NaturalOrder` or `@Value.ReverseOrder`",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ImmutablesSortedSetComparator extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodTree> METHOD_LACKS_ANNOTATION =
allOf(
methodReturns(isSubtypeOf(SortedSet.class)),
anyOf(
allOf(
hasModifier(Modifier.ABSTRACT),
enclosingClass(
anyOf(
hasAnnotation("org.immutables.value.Value.Immutable"),
hasAnnotation("org.immutables.value.Value.Modifiable")))),
hasAnnotation("org.immutables.value.Value.Default")),
not(
anyOf(
hasAnnotation("org.immutables.value.Value.NaturalOrder"),
hasAnnotation("org.immutables.value.Value.ReverseOrder"))));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!METHOD_LACKS_ANNOTATION.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder builder = SuggestedFix.builder();
String valueTypeIdentifier =
SuggestedFixes.qualifyType(state, builder, "org.immutables.value.Value");
return describeMatch(
tree,
builder.prefixWith(tree, String.format("@%s.NaturalOrder ", valueTypeIdentifier)).build());
}
}

View File

@@ -1,200 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
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 java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isReservedKeyword;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
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 java.util.Optional;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check which enforces that test classes:
// 1. Are named `*Test` or `Abstract*TestCase`.
// 2. If not `abstract`, are package-private and don't have public methods and subclasses.
// 3. Only have private fields.
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
@AutoService(BugChecker.class)
@BugPattern(
summary = "JUnit method declaration can likely be improved",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class JUnitMethodDeclaration extends BugChecker implements MethodTreeMatcher {
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))));
private static final MultiMatcher<MethodTree, AnnotationTree> TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
private static final MultiMatcher<MethodTree, AnnotationTree> SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (HAS_UNMODIFIABLE_SIGNATURE.matches(tree, state)) {
return Description.NO_MATCH;
}
boolean isTestMethod = TEST_METHOD.matches(tree, state);
if (!isTestMethod && !SETUP_OR_TEARDOWN_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder fixBuilder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(tree.getModifiers(), state, ILLEGAL_MODIFIERS)
.ifPresent(fixBuilder::merge);
if (isTestMethod) {
suggestTestMethodRenameIfApplicable(tree, fixBuilder, state);
}
return fixBuilder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fixBuilder.build());
}
private void suggestTestMethodRenameIfApplicable(
MethodTree tree, SuggestedFix.Builder fixBuilder, VisitorState state) {
tryCanonicalizeMethodName(tree)
.ifPresent(
newName ->
findMethodRenameBlocker(newName, state)
.ifPresentOrElse(
blocker -> reportMethodRenameBlocker(tree, blocker, state),
() -> fixBuilder.merge(SuggestedFixes.renameMethod(tree, newName, state))));
}
private void reportMethodRenameBlocker(MethodTree tree, String reason, VisitorState state) {
state.reportMatch(
buildDescription(tree)
.setMessage(
String.format(
"This method's name should not redundantly start with `%s` (but note that %s)",
TEST_PREFIX, reason))
.build());
}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration.
* <li>Whether the rename would cause a method in a superclass to be overridden.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(String methodName, VisitorState state) {
if (isMethodInEnclosingClass(methodName, state)) {
return Optional.of(
String.format("a method named `%s` already exists in this class", methodName));
}
if (isSimpleNameStaticallyImported(methodName, state)) {
return Optional.of(String.format("`%s` is already statically imported", methodName));
}
if (isReservedKeyword(methodName)) {
return Optional.of(String.format("`%s` is a reserved keyword", methodName));
}
return Optional.empty();
}
private static boolean isMethodInEnclosingClass(String methodName, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.map(MethodTree::getName)
.map(Name::toString)
.anyMatch(methodName::equals);
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(MethodTree tree) {
return Optional.of(ASTHelpers.getSymbol(tree).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)));
}
// XXX: Move to a `MoreMatchers` utility class.
private static Matcher<AnnotationTree> hasMetaAnnotation(String annotationClassName) {
TypePredicate typePredicate = hasAnnotation(annotationClassName);
return (tree, state) -> {
Symbol sym = ASTHelpers.getSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Move to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -0,0 +1,114 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static java.util.function.Predicate.not;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol;
import java.util.Optional;
import javax.lang.model.element.Modifier;
/** A {@link BugChecker} which flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check which enforces that test classes:
// 1. Are named `*Test` or `Abstract*TestCase`.
// 2. If not `abstract`, don't have public methods and subclasses.
// 3. Only have private fields.
// XXX: If implemented, the current logic could flag only `private` JUnit methods.
@AutoService(BugChecker.class)
@BugPattern(
name = "JUnitMethodDeclaration",
summary = "JUnit method declaration can likely be improved",
linkType = BugPattern.LinkType.NONE,
severity = BugPattern.SeverityLevel.SUGGESTION,
tags = BugPattern.StandardTags.SIMPLIFICATION)
public final class JUnitMethodDeclarationCheck extends BugChecker implements MethodTreeMatcher {
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 MultiMatcher<MethodTree, AnnotationTree> IS_OVERRIDE_METHOD =
annotations(AT_LEAST_ONE, isType("java.lang.Override"));
private static final MultiMatcher<MethodTree, AnnotationTree> IS_TEST_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.Test"),
hasMetaAnnotation("org.junit.jupiter.api.TestTemplate")));
private static final MultiMatcher<MethodTree, AnnotationTree> IS_SETUP_OR_TEARDOWN_METHOD =
annotations(
AT_LEAST_ONE,
anyOf(
isType("org.junit.jupiter.api.AfterAll"),
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
// XXX: Perhaps we should also skip analysis of non-`private` non-`final` methods in abstract
// classes?
if (IS_OVERRIDE_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
boolean isTestMethod = IS_TEST_METHOD.matches(tree, state);
if (!isTestMethod && !IS_SETUP_OR_TEARDOWN_METHOD.matches(tree, state)) {
return Description.NO_MATCH;
}
SuggestedFix.Builder builder = SuggestedFix.builder();
SuggestedFixes.removeModifiers(tree.getModifiers(), state, ILLEGAL_MODIFIERS)
.ifPresent(builder::merge);
if (isTestMethod) {
// XXX: In theory this rename could clash with an existing method or static import. In that
// case we should emit a warning without a suggested replacement.
tryCanonicalizeMethodName(tree, state).ifPresent(builder::merge);
}
return builder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, builder.build());
}
private static Optional<SuggestedFix> tryCanonicalizeMethodName(
MethodTree tree, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(sym -> sym.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)))
.map(name -> SuggestedFixes.renameMethod(tree, name, state));
}
// XXX: Move to a `MoreMatchers` utility class.
private static Matcher<AnnotationTree> hasMetaAnnotation(String annotationClassName) {
TypePredicate typePredicate = hasAnnotation(annotationClassName);
return (tree, state) -> {
Symbol sym = ASTHelpers.getDeclaredSymbol(tree);
return sym != null && typePredicate.apply(sym.type, state);
};
}
// XXX: Move to a `MoreTypePredicates` utility class.
private static TypePredicate hasAnnotation(String annotationClassName) {
return (type, state) -> ASTHelpers.hasAnnotation(type.tsym, annotationClassName, state);
}
}

View File

@@ -1,9 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.joining;
@@ -12,6 +8,9 @@ import com.google.common.collect.Comparators;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -32,14 +31,12 @@ import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags annotation array listings which aren't sorted lexicographically.
@@ -49,11 +46,12 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "LexicographicalAnnotationAttributeListing",
summary = "Where possible, sort annotation array attributes lexicographically",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationAttributeListing extends BugChecker
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.STYLE)
public final class LexicographicalAnnotationAttributeListingCheck extends BugChecker
implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final ImmutableSet<String> BLACKLISTED_ANNOTATIONS =
@@ -62,27 +60,24 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
"com.fasterxml.jackson.annotation.JsonPropertyOrder#value",
"io.swagger.annotations.ApiImplicitParams#value",
"io.swagger.v3.oas.annotations.Parameters#value",
"javax.xml.bind.annotation.XmlType#propOrder",
"org.springframework.context.annotation.PropertySource#value",
"org.springframework.test.context.TestPropertySource#locations",
"org.springframework.test.context.TestPropertySource#value");
"javax.xml.bind.annotation.XmlType#propOrder");
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListing:";
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
private final AnnotationAttributeMatcher matcher;
/** Instantiates the default {@link LexicographicalAnnotationAttributeListing}. */
public LexicographicalAnnotationAttributeListing() {
/** Instantiates the default {@link LexicographicalAnnotationAttributeListingCheck}. */
public LexicographicalAnnotationAttributeListingCheck() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link LexicographicalAnnotationAttributeListing}.
* Instantiates a customized {@link LexicographicalAnnotationAttributeListingCheck}.
*
* @param flags Any provided command line flags.
*/
public LexicographicalAnnotationAttributeListing(ErrorProneFlags flags) {
public LexicographicalAnnotationAttributeListingCheck(ErrorProneFlags flags) {
matcher = createAnnotationAttributeMatcher(flags);
}
@@ -133,7 +128,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
/* The elements aren't sorted. Suggest the sorted alternative. */
String suggestion =
desiredOrdering.stream()
.map(expr -> SourceCode.treeToString(expr, state))
.map(expr -> Util.treeToString(expr, state))
.collect(joining(", ", "{", "}"));
return Optional.of(SuggestedFix.builder().replace(array, suggestion));
}
@@ -158,7 +153,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
// XXX: Perhaps we should use `Collator` with `.setStrength(Collator.PRIMARY)` and
// `getCollationKey`. Not clear whether that's worth the hassle at this point.
return ImmutableList.sortedCopyOf(
comparing(
Comparator.comparing(
e -> getStructure(e, state),
Comparators.lexicographical(
Comparators.lexicographical(
@@ -176,23 +171,20 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
ImmutableList.Builder<ImmutableList<String>> nodes = ImmutableList.builder();
new TreeScanner<Void, Void>() {
@Nullable
@Override
public Void visitIdentifier(IdentifierTree node, @Nullable Void ctx) {
public Void visitIdentifier(IdentifierTree node, Void ctx) {
nodes.add(tokenize(node));
return super.visitIdentifier(node, ctx);
}
@Nullable
@Override
public Void visitLiteral(LiteralTree node, @Nullable Void ctx) {
public Void visitLiteral(LiteralTree node, Void ctx) {
nodes.add(tokenize(node));
return super.visitLiteral(node, ctx);
}
@Nullable
@Override
public Void visitPrimitiveType(PrimitiveTypeTree node, @Nullable Void ctx) {
public Void visitPrimitiveType(PrimitiveTypeTree node, Void ctx) {
nodes.add(tokenize(node));
return super.visitPrimitiveType(node, ctx);
}
@@ -202,7 +194,7 @@ public final class LexicographicalAnnotationAttributeListing extends BugChecker
* Tokens are split on `=` so that e.g. inline Spring property declarations are properly
* sorted by key, then value.
*/
return ImmutableList.copyOf(SourceCode.treeToString(node, state).split("=", -1));
return ImmutableList.copyOf(Util.treeToString(node, state).split("=", -1));
}
}.scan(array, null);

View File

@@ -1,80 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static java.util.Comparator.comparing;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
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.MethodTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags annotations that are not lexicographically sorted.
*
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
* resolution, and can even avoid it if two branches add the same annotation.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Sort annotations lexicographically where possible",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class LexicographicalAnnotationListing extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
List<? extends AnnotationTree> originalOrdering = tree.getModifiers().getAnnotations();
if (originalOrdering.size() < 2) {
return Description.NO_MATCH;
}
ImmutableList<? extends AnnotationTree> sortedAnnotations = sort(originalOrdering, state);
if (originalOrdering.equals(sortedAnnotations)) {
return Description.NO_MATCH;
}
Optional<Fix> fix = tryFixOrdering(originalOrdering, sortedAnnotations, state);
Description.Builder description = buildDescription(originalOrdering.get(0));
fix.ifPresent(description::addFix);
return description.build();
}
private static ImmutableList<? extends AnnotationTree> sort(
List<? extends AnnotationTree> annotations, VisitorState state) {
return annotations.stream()
.sorted(comparing(annotation -> SourceCode.treeToString(annotation, state)))
.collect(toImmutableList());
}
private static Optional<Fix> tryFixOrdering(
List<? extends AnnotationTree> originalAnnotations,
ImmutableList<? extends AnnotationTree> sortedAnnotations,
VisitorState state) {
return Streams.zip(
originalAnnotations.stream(),
sortedAnnotations.stream(),
(original, replacement) ->
SuggestedFix.builder()
.replace(original, SourceCode.treeToString(replacement, state)))
.reduce(SuggestedFix.Builder::merge)
.map(SuggestedFix.Builder::build);
}
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns.util;
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
@@ -15,20 +15,13 @@ import java.util.regex.Pattern;
/** A method invocation expression {@link Matcher} factory. */
// XXX: Document better. The expressions accepted here could also be defined using `MethodMatchers`.
// So explain why this class is still useful.
public final class MethodMatcherFactory {
final class MethodMatcherFactory {
private static final Splitter ARGUMENT_TYPE_SPLITTER =
Splitter.on(',').trimResults().omitEmptyStrings();
private static final Pattern METHOD_SIGNATURE =
Pattern.compile("([^\\s#(,)]+)#([^\\s#(,)]+)\\(((?:[^\\s#(,)]+(?:,[^\\s#(,)]+)*)?)\\)");
/**
* Creates a {@link Matcher} of methods with any of the given signatures.
*
* @param signatures The method signatures of interest.
* @return A new {@link Matcher} which accepts invocation expressions of any method identified by
* the given signatures.
*/
public Matcher<ExpressionTree> create(Collection<String> signatures) {
Matcher<ExpressionTree> create(Collection<String> signatures) {
return anyOf(
signatures.stream()
.map(MethodMatcherFactory::createMethodMatcher)

View File

@@ -1,14 +1,14 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.LambdaExpressionTreeMatcher;
@@ -47,15 +47,15 @@ import javax.lang.model.element.Name;
// black-and-white. Maybe we can more closely approximate it?
// XXX: With Java 9's introduction of `Predicate.not`, we could write many lambda expressions to
// `not(some::reference)`.
// XXX: This check is extremely inefficient due to its reliance on `SuggestedFixes.compilesWithFix`.
// Palantir's `LambdaMethodReference` check seems to suffer a similar issue at this time.
@AutoService(BugChecker.class)
@BugPattern(
name = "MethodReferenceUsage",
summary = "Prefer method references over lambda expressions",
linkType = NONE,
severity = SUGGESTION,
tags = STYLE)
public final class MethodReferenceUsage extends BugChecker implements LambdaExpressionTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.STYLE)
public final class MethodReferenceUsageCheck extends BugChecker
implements LambdaExpressionTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
@@ -67,10 +67,7 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
*/
return constructMethodRef(tree, tree.getBody())
.map(SuggestedFix.Builder::build)
.filter(
fix ->
SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true))
.filter(fix -> SuggestedFixes.compilesWithFix(fix, state))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
@@ -100,8 +97,6 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
.flatMap(statements -> constructMethodRef(lambdaExpr, statements.get(0)));
}
// XXX: Replace nested `Optional` usage.
@SuppressWarnings("NestedOptionals")
private static Optional<SuggestedFix.Builder> constructMethodRef(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
return matchArguments(lambdaExpr, subTree)
@@ -158,8 +153,6 @@ public final class MethodReferenceUsage extends BugChecker implements LambdaExpr
return constructFix(lambdaExpr, lhsType.tsym, subTree.getIdentifier());
}
// XXX: Refactor or replace inner `Optional` with a custom type.
@SuppressWarnings("NestedOptionals")
private static Optional<Optional<Name>> matchArguments(
LambdaExpressionTree lambdaExpr, MethodInvocationTree subTree) {
ImmutableList<Name> expectedArguments = getVariables(lambdaExpr);

View File

@@ -1,8 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
@@ -10,12 +7,14 @@ import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
@@ -24,13 +23,14 @@ import com.sun.source.tree.Tree;
/** A {@link BugChecker} that flags likely missing Refaster annotations. */
@AutoService(BugChecker.class)
@BugPattern(
name = "MissingRefasterAnnotation",
summary = "The Refaster template contains a method without any Refaster annotations",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class MissingRefasterAnnotation extends BugChecker implements ClassTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.LIKELY_ERROR)
public final class MissingRefasterAnnotationCheck extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
private static final MultiMatcher<Tree, AnnotationTree> REFASTER_ANNOTATION =
private static final MultiMatcher<Tree, AnnotationTree> HAS_REFASTER_ANNOTATION =
annotations(
AT_LEAST_ONE,
anyOf(
@@ -44,8 +44,7 @@ public final class MissingRefasterAnnotation extends BugChecker implements Class
tree.getMembers().stream()
.filter(member -> member.getKind() == Tree.Kind.METHOD)
.map(MethodTree.class::cast)
.filter(method -> !ASTHelpers.isGeneratedConstructor(method))
.map(method -> REFASTER_ANNOTATION.matches(method, state))
.map(method -> HAS_REFASTER_ANNOTATION.matches(method, state))
.distinct()
.count();

View File

@@ -1,58 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.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.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags method invocations for which all arguments are wrapped using
* {@link org.mockito.Mockito#eq}; this is redundant.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Don't unnecessarily use Mockito's `eq(...)`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoStubbing extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MOCKITO_EQ_METHOD =
staticMethod().onClass("org.mockito.ArgumentMatchers").named("eq");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
if (arguments.isEmpty() || !arguments.stream().allMatch(arg -> isEqInvocation(arg, state))) {
return Description.NO_MATCH;
}
SuggestedFix.Builder suggestedFix = SuggestedFix.builder();
for (ExpressionTree arg : arguments) {
suggestedFix.replace(
arg,
SourceCode.treeToString(
Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments()), state));
}
return describeMatch(tree, suggestedFix.build());
}
private static boolean isEqInvocation(ExpressionTree tree, VisitorState state) {
return tree instanceof MethodInvocationTree && MOCKITO_EQ_METHOD.matches(tree, state);
}
}

View File

@@ -1,383 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableTable.toImmutableTable;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.REFACTORING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static java.util.Objects.requireNonNull;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.auto.value.AutoValue.CopyAnnotations;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableTable;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.annotations.Var;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.IdentifierTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.predicates.TypePredicates;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.jvm.Target;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} which flags the same legacy APIs as the <a
* href="https://github.com/gaul/modernizer-maven-plugin">Modernizer Maven Plugin</a>.
*
* <p>This checker is primarily useful for people who run Error Prone anyway; it obviates the need
* for an additional source code analysis pass using another Maven plugin.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid constants and methods superceded by more recent equivalents",
linkType = NONE,
severity = SUGGESTION,
tags = REFACTORING)
public final class Modernizer extends BugChecker
implements AnnotationTreeMatcher,
IdentifierTreeMatcher,
MemberReferenceTreeMatcher,
MemberSelectTreeMatcher,
NewClassTreeMatcher {
private static final long serialVersionUID = 1L;
// XXX: Load lazily?
private final ImmutableTable<String, Matcher<ExpressionTree>, String> violations =
loadViolations();
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
// XXX: Use or drop
// XXX: See
// https://github.com/google/guava-beta-checker/commit/9b26aa980be7f70631921fd6695013547728eb1e;
// we may be on the right track without this.
return Description.NO_MATCH;
}
@Override
public Description matchIdentifier(IdentifierTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
return match(tree.getIdentifier(), tree, state);
}
@Override
public Description matchNewClass(NewClassTree tree, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(Symbol::getEnclosingElement)
.map(Symbol::getQualifiedName)
.map(name -> match(name, tree, state))
.orElse(Description.NO_MATCH);
}
private Description match(Name identifier, ExpressionTree tree, VisitorState state) {
return this.violations.row(identifier.toString()).entrySet().stream()
.filter(e -> e.getKey().matches(tree, state))
.findFirst()
.map(e -> buildDescription(tree).setMessage(e.getValue()).build())
.orElse(Description.NO_MATCH);
}
private ImmutableTable<String, Matcher<ExpressionTree>, String> loadViolations() {
InputStream resource = getClass().getResourceAsStream("/modernizer.xml");
// XXX: Or silently skip?
checkState(resource != null, "Modernizer configuration not found on classpath");
XmlMapper mapper = new XmlMapper();
try (resource) {
return mapper.readValue(resource, Violations.class).getViolation().stream()
.filter(v -> v.getChecker().isPresent())
.collect(
toImmutableTable(
Violation::getIdentifier,
v -> v.getChecker().orElseThrow(),
Violation::getComment));
} catch (IOException e) {
throw new UncheckedIOException("Failed to parse Modernizer configuration", e);
}
}
// XXX: Further simplify with Auto Value?
@Immutable
static final class Violations {
@JacksonXmlElementWrapper(useWrapping = false)
private final ImmutableList<Violation> violation;
@JsonCreator
private Violations(@JsonProperty("violation") List<Violation> violation) {
this.violation = ImmutableList.copyOf(violation);
}
// XXX: Jackson relies on this naming and visibility. Ugh.
public ImmutableList<Violation> getViolation() {
return violation;
}
}
@Immutable
@AutoValue
abstract static class Violation {
private static final Pattern NAME_PATTERN =
Pattern.compile(
"(?<type>[^.]+)(?:\\.(?<member>[^:]+):(?:\\((?<params>[^)]*)\\))?(?<return>[^()]+))?");
abstract Optional<Target> getTarget();
abstract String getIdentifier();
@CopyAnnotations
@SuppressWarnings("Immutable")
abstract Matcher<ExpressionTree> getMatcher();
abstract String getComment();
Optional<Matcher<ExpressionTree>> getChecker() {
return getTarget().map(t -> allOf(getMatcher(), targetMatcher(t)));
}
// XXX: Overkill? Not if we use auto value.
// XXX: Modernizer also flags annotation declarations, presumably by type.
// XXX: `ExpressionTree` is wrong here. Depends on type.
@JsonCreator
static Violation create(
@JsonProperty("version") String version,
@JsonProperty("name") String signature,
@JsonProperty("comment") String comment) {
Optional<Target> target = Optional.ofNullable(Target.lookup(version));
java.util.regex.Matcher matcher = NAME_PATTERN.matcher(signature);
checkState(matcher.matches(), "Failed to parse signature '%s'", signature);
String type =
replaceSlahes(requireNonNull(matcher.group("type"), "Signature must contain type"));
String member = matcher.group("member");
if (member == null) {
// XXX: Should not implement this interface. Something like:
// violations.put(type, allOf(isSubtypeOf(type), versionRequirement), this.comment)
return new AutoValue_Modernizer_Violation(target, type, (t, s) -> false, comment);
}
String params = matcher.group("params");
if (params == null) {
return new AutoValue_Modernizer_Violation(target, member, isField(type), comment);
}
ImmutableList<Supplier<Type>> parameters = parseParams(params);
if ("\"<init>\"".equals(member)) {
return new AutoValue_Modernizer_Violation(
target, type, isConstructor(type, parameters), comment);
}
// XXX: Should we disallow _extension_ of this method?
return new AutoValue_Modernizer_Violation(
target, member, isMethod(type, parameters), comment);
}
private static Matcher<ExpressionTree> targetMatcher(Target target) {
return (tree, state) -> target.compareTo(getTargetVersion(state)) <= 0;
}
private static Target getTargetVersion(VisitorState state) {
return Target.instance(
Optional.ofNullable(state.context.get(JavacTask.class))
.filter(BasicJavacTask.class::isInstance)
.map(BasicJavacTask.class::cast)
.map(BasicJavacTask::getContext)
.orElse(state.context));
}
private static Matcher<ExpressionTree> isField(String onDescendantOf) {
return isMember(
ElementKind::isField, TypePredicates.isDescendantOf(onDescendantOf), ImmutableList.of());
}
private static Matcher<ExpressionTree> isConstructor(
String ofClass, ImmutableList<Supplier<Type>> withParameters) {
return isMember(
k -> k == ElementKind.CONSTRUCTOR, TypePredicates.isExactType(ofClass), withParameters);
}
private static Matcher<ExpressionTree> isMethod(
String onDescendantOf, ImmutableList<Supplier<Type>> withParameters) {
return isMember(
k -> k == ElementKind.METHOD,
TypePredicates.isDescendantOf(onDescendantOf),
withParameters);
}
private static Matcher<ExpressionTree> isMember(
Predicate<ElementKind> ofKind,
TypePredicate ownedBy,
ImmutableList<Supplier<Type>> withParameters) {
return (tree, state) ->
Optional.ofNullable(ASTHelpers.getSymbol(tree))
.filter(s -> ofKind.test(s.getKind()))
.filter(s -> isOwnedBy(s, ownedBy, state))
.filter(s -> hasSameParameters(s, withParameters, state))
.isPresent();
}
private static boolean isOwnedBy(Symbol symbol, TypePredicate expected, VisitorState state) {
Symbol owner = symbol.getEnclosingElement();
return owner != null && expected.apply(owner.asType(), state);
}
private static boolean hasSameParameters(
Symbol method, ImmutableList<Supplier<Type>> expected, VisitorState state) {
List<Type> actual = method.asType().getParameterTypes();
if (actual.size() != expected.size()) {
return false;
}
for (int i = 0; i < actual.size(); ++i) {
if (!ASTHelpers.isSameType(actual.get(i), expected.get(i).get(state), state)) {
return false;
}
}
return true;
}
private static ImmutableList<Supplier<Type>> parseParams(String params) {
ImmutableList.Builder<Supplier<Type>> types = ImmutableList.builder();
@Var int index = 0;
while (index < params.length()) {
index = parseType(params, index, types::add);
}
return types.build();
}
private static int parseType(String params, int index, Consumer<Supplier<Type>> sink) {
switch (params.charAt(index)) {
case '[':
return parseArrayType(params, index, sink);
case 'L':
return parseTypeReference(params, index, sink);
default:
return parsePrimitiveType(params, index, sink);
}
}
private static int parseArrayType(String params, int index, Consumer<Supplier<Type>> sink) {
int typeIndex = index + 1;
checkArgument(
params.length() > typeIndex && params.charAt(index) == '[',
"Cannot parse array type in parameter string '%s' at index %s",
params,
index);
return parseType(
params,
typeIndex,
type ->
sink.accept(s -> s.getType(type.get(s), /* isArray= */ true, ImmutableList.of())));
}
private static int parsePrimitiveType(String params, int index, Consumer<Supplier<Type>> sink) {
String primitive =
Optional.of(params)
.filter(p -> p.length() > index)
.flatMap(p -> fromPrimitiveAlias(p.charAt(index)))
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Cannot parse primitive type in parameter string '%s' at index %s",
params, index)));
sink.accept(s -> s.getTypeFromString(primitive));
return index + 1;
}
private static Optional<String> fromPrimitiveAlias(char alias) {
switch (alias) {
case 'Z':
return Optional.of("boolean");
case 'B':
return Optional.of("byte");
case 'C':
return Optional.of("char");
case 'S':
return Optional.of("short");
case 'I':
return Optional.of("int");
case 'J':
return Optional.of("long");
case 'F':
return Optional.of("float");
case 'D':
return Optional.of("double");
default:
return Optional.empty();
}
}
private static int parseTypeReference(String params, int index, Consumer<Supplier<Type>> sink) {
int identifierIndex = index + 1;
if (params.length() > identifierIndex && params.charAt(index) == 'L') {
int delimiter = params.indexOf(';', identifierIndex);
if (delimiter > index) {
sink.accept(
s ->
s.getTypeFromString(replaceSlahes(params.substring(identifierIndex, delimiter))));
return delimiter + 1;
}
}
throw new IllegalArgumentException(
String.format(
"Cannot parse reference type in parameter string '%s' at index %s", params, index));
}
private static String replaceSlahes(String typeName) {
return typeName.replace('/', '.');
}
}
}

View File

@@ -1,51 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
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.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.List;
import java.util.Optional;
/** A {@link BugChecker} which flags nesting of {@link Optional Optionals}. */
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Avoid nesting `Optional`s inside `Optional`s; the resultant code is hard to reason about",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
Type optionalType = OPTIONAL.get(state);
Type type = ASTHelpers.getType(tree);
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
return false;
}
List<Type> typeArguments = type.getTypeArguments();
return !typeArguments.isEmpty()
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
}
}

View File

@@ -1,16 +1,16 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.stream.Collectors.joining;
import static java.util.function.Predicate.not;
import com.google.auto.service.AutoService;
import com.google.common.base.VerifyException;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
@@ -30,8 +30,6 @@ import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import java.util.Comparator;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code Comparator#comparing*} invocations that can be replaced
@@ -39,15 +37,18 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
*/
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more
// specific types.
// XXX: Change this checker's name?
@AutoService(BugChecker.class)
@BugPattern(
name = "PrimitiveComparison",
summary =
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
+ " of the provided function",
linkType = NONE,
severity = WARNING,
tags = PERFORMANCE)
public final class PrimitiveComparison extends BugChecker implements MethodInvocationTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.PERFORMANCE)
public final class PrimitiveComparisonCheck extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> STATIC_COMPARISON_METHOD =
anyOf(
@@ -76,52 +77,23 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
}
return getPotentiallyBoxedReturnType(tree.getArguments().get(0))
.flatMap(cmpType -> attemptMethodInvocationReplacement(tree, cmpType, isStatic, state))
.flatMap(cmpType -> tryFix(tree, state, cmpType, isStatic))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional<Fix> attemptMethodInvocationReplacement(
MethodInvocationTree tree, Type cmpType, boolean isStatic, VisitorState state) {
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
String preferredMethodName = getPreferredMethod(cmpType, isStatic, state);
if (actualMethodName.equals(preferredMethodName)) {
return Optional.empty();
}
return Optional.of(
suggestFix(
tree, prefixTypeArgumentsIfRelevant(preferredMethodName, tree, cmpType, state), state));
private static Optional<Fix> tryFix(
MethodInvocationTree tree, VisitorState state, Type cmpType, boolean isStatic) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(methodSymbol -> methodSymbol.getSimpleName().toString())
.flatMap(
actualMethodName ->
Optional.of(getPreferredMethod(state, cmpType, isStatic))
.filter(not(actualMethodName::equals)))
.map(preferredMethodName -> suggestFix(tree, preferredMethodName, state));
}
/**
* Prefixes the given method name with generic type parameters if it replaces a {@code
* Comparator#comparing{,Double,Long,Int}} method which also has generic type parameters.
*
* <p>Such type parameters are retained as they are likely required.
*
* <p>Note that any type parameter to {@code Comparator#thenComparing} is likely redundant, and in
* any case becomes obsolete once that method is replaced with {@code
* Comparator#thenComparing{Double,Long,Int}}. Conversion in the opposite direction does not
* require the introduction of a generic type parameter.
*/
private static String prefixTypeArgumentsIfRelevant(
String preferredMethodName, MethodInvocationTree tree, Type cmpType, VisitorState state) {
if (tree.getTypeArguments().isEmpty() || preferredMethodName.startsWith("then")) {
return preferredMethodName;
}
String typeArguments =
Stream.concat(
Stream.of(SourceCode.treeToString(tree.getTypeArguments().get(0), state)),
Stream.of(cmpType.tsym.getSimpleName())
.filter(u -> "comparing".equals(preferredMethodName)))
.collect(joining(", ", "<", ">"));
return typeArguments + preferredMethodName;
}
private static String getPreferredMethod(Type cmpType, boolean isStatic, VisitorState state) {
private static String getPreferredMethod(VisitorState state, Type cmpType, boolean isStatic) {
Types types = state.getTypes();
Symtab symtab = state.getSymtab();
@@ -156,6 +128,9 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
}
}
// XXX: We drop explicitly specified generic type information. In case the number of type
// arguments before and after doesn't match, that's for the better. But if we e.g. replace
// `comparingLong` with `comparingInt`, then we should retain it.
private static Fix suggestFix(
MethodInvocationTree tree, String preferredMethodName, VisitorState state) {
ExpressionTree expr = tree.getMethodSelect();
@@ -168,7 +143,7 @@ public final class PrimitiveComparison extends BugChecker implements MethodInvoc
case MEMBER_SELECT:
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
return SuggestedFix.replace(
ms, SourceCode.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
ms, Util.treeToString(ms.getExpression(), state) + '.' + preferredMethodName);
default:
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
}

View File

@@ -1,11 +1,8 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
import static com.google.errorprone.matchers.Matchers.isNonNull;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.not;
@@ -15,6 +12,9 @@ import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -41,17 +41,16 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags redundant explicit string conversions. */
@AutoService(BugChecker.class)
@BugPattern(
name = "RedundantStringConversion",
summary = "Avoid redundant string conversions when possible",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RedundantStringConversion extends BugChecker
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class RedundantStringConversionCheck extends BugChecker
implements BinaryTreeMatcher, CompoundAssignmentTreeMatcher, MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String FLAG_PREFIX = "RedundantStringConversion:";
@@ -65,8 +64,7 @@ public final class RedundantStringConversion extends BugChecker
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> STRING = isSameType(String.class);
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
private static final Matcher<ExpressionTree> NON_NULL_STRING =
allOf(STRING, isNonNullUsingDataflow());
private static final Matcher<ExpressionTree> NON_NULL_STRING = allOf(STRING, isNonNull());
private static final Matcher<ExpressionTree> NOT_FORMATTABLE =
not(isSubtypeOf(Formattable.class));
private static final Matcher<ExpressionTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
@@ -158,17 +156,17 @@ public final class RedundantStringConversion extends BugChecker
private final Matcher<ExpressionTree> conversionMethodMatcher;
/** Instantiates the default {@link RedundantStringConversion}. */
public RedundantStringConversion() {
/** Instantiates the default {@link RedundantStringConversionCheck}. */
public RedundantStringConversionCheck() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link RedundantStringConversion}.
* Instantiates a customized {@link RedundantStringConversionCheck}.
*
* @param flags Any provided command line flags.
*/
public RedundantStringConversion(ErrorProneFlags flags) {
public RedundantStringConversionCheck(ErrorProneFlags flags) {
conversionMethodMatcher = createConversionMethodMatcher(flags);
}
@@ -327,7 +325,7 @@ public final class RedundantStringConversion extends BugChecker
return trySimplify(tree, state, filter)
.map(
replacement ->
SuggestedFix.builder().replace(tree, SourceCode.treeToString(replacement, state)));
SuggestedFix.builder().replace(tree, Util.treeToString(replacement, state)));
}
private Optional<ExpressionTree> trySimplify(
@@ -351,7 +349,7 @@ public final class RedundantStringConversion extends BugChecker
default:
throw new IllegalStateException(
"Cannot simplify method call with two or more arguments: "
+ SourceCode.treeToString(tree, state));
+ Util.treeToString(tree, state));
}
}
@@ -364,7 +362,7 @@ public final class RedundantStringConversion extends BugChecker
return Optional.of(methodInvocation.getMethodSelect())
.filter(methodSelect -> methodSelect.getKind() == Kind.MEMBER_SELECT)
.map(methodSelect -> ((MemberSelectTree) methodSelect).getExpression())
.filter(expr -> !"super".equals(SourceCode.treeToString(expr, state)));
.filter(expr -> !"super".equals(Util.treeToString(expr, state)));
}
private static Optional<ExpressionTree> trySimplifyUnaryMethod(

View File

@@ -1,57 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.refaster.Refaster;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags unnecessary {@link Refaster#anyOf(Object[])} usages.
*
* <p>Note that this logic can't be implemented as a Refaster template, as the {@link Refaster}
* class is treated specially.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "`Refaster#anyOf` should be passed at least two parameters",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class RefasterAnyOfUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> REFASTER_ANY_OF =
staticMethod().onClass(Refaster.class.getName()).named("anyOf");
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (REFASTER_ANY_OF.matches(tree, state)) {
switch (tree.getArguments().size()) {
case 0:
// We can't safely fix this case; dropping the expression may produce non-compilable code.
return describeMatch(tree);
case 1:
return describeMatch(
tree,
SuggestedFix.replace(
tree, SourceCode.treeToString(tree.getArguments().get(0), state)));
default:
/* Handled below. */
}
}
return Description.NO_MATCH;
}
}

View File

@@ -1,27 +1,30 @@
package tech.picnic.errorprone.refaster.runner;
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableRangeSet.toImmutableRangeSet;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.function.Predicate.not;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ResourceInfo;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.CodeTransformer;
import com.google.errorprone.CompositeCodeTransformer;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.SubContext;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.fixes.Replacement;
@@ -29,10 +32,17 @@ import com.google.errorprone.matchers.Description;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -46,47 +56,43 @@ import java.util.stream.Stream;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "Refaster",
summary = "Write idiomatic code when possible",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class Refaster extends BugChecker implements CompilationUnitTreeMatcher {
/** Flag to pass a pattern that restricts which Refaster templates are loaded. */
public static final String INCLUDED_TEMPLATES_PATTERN_FLAG = "Refaster:NamePattern";
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class RefasterCheck extends BugChecker implements CompilationUnitTreeMatcher {
private static final String REFASTER_TEMPLATE_SUFFIX = ".refaster";
private static final String INCLUDED_TEMPLATES_PATTERN_FLAG = "Refaster:NamePattern";
private static final long serialVersionUID = 1L;
private final CodeTransformer codeTransformer;
/** Instantiates the default {@link Refaster}. */
public Refaster() {
/** Instantiates the default {@link RefasterCheck}. */
public RefasterCheck() {
this(ErrorProneFlags.empty());
}
/**
* Instantiates a customized {@link Refaster}.
* Instantiates a customized {@link RefasterCheck}.
*
* @param flags Any provided command line flags.
*/
public Refaster(ErrorProneFlags flags) {
codeTransformer = createCompositeCodeTransformer(flags);
public RefasterCheck(ErrorProneFlags flags) {
codeTransformer = createCompositeCodeTransformer(flags, loadAllCodeTransformers());
}
@VisibleForTesting
RefasterCheck(
ErrorProneFlags flags, ImmutableListMultimap<String, CodeTransformer> allTransformers) {
codeTransformer = createCompositeCodeTransformer(flags, allTransformers);
}
@CanIgnoreReturnValue
@Override
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
/* First, collect all matches. */
List<Description> matches = new ArrayList<>();
try {
codeTransformer.apply(state.getPath(), new SubContext(state.context), matches::add);
} catch (LinkageError e) {
// XXX: This `try/catch` block handles the issue described and resolved in
// https://github.com/google/error-prone/pull/2456. Drop this block once that change is
// released.
// XXX: Find a way to identify that we're running Picnic's Error Prone fork and disable this
// fallback if so, as it might hide other bugs.
return Description.NO_MATCH;
}
codeTransformer.apply(state.getPath(), new SubContext(state.context), matches::add);
/* Then apply them. */
applyMatches(matches, ((JCCompilationUnit) tree).endPositions, state);
@@ -146,23 +152,73 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat
return description.fixes.stream().flatMap(fix -> fix.getReplacements(endPositions).stream());
}
private static CodeTransformer createCompositeCodeTransformer(ErrorProneFlags flags) {
ImmutableListMultimap<String, CodeTransformer> allTransformers =
CodeTransformers.getAllCodeTransformers();
private static CodeTransformer createCompositeCodeTransformer(
ErrorProneFlags flags, ImmutableListMultimap<String, CodeTransformer> allTransformers) {
return CompositeCodeTransformer.compose(
flags
.get(INCLUDED_TEMPLATES_PATTERN_FLAG)
.map(Pattern::compile)
.<ImmutableCollection<CodeTransformer>>map(
nameFilter -> filterCodeTransformers(allTransformers, nameFilter))
.map(nameFilter -> filterCodeTransformers(allTransformers, nameFilter))
.orElseGet(allTransformers::values));
}
private static ImmutableList<CodeTransformer> filterCodeTransformers(
private static ImmutableCollection<CodeTransformer> filterCodeTransformers(
ImmutableListMultimap<String, CodeTransformer> transformers, Pattern nameFilter) {
return transformers.entries().stream()
.filter(e -> nameFilter.matcher(e.getKey()).matches())
.map(Map.Entry::getValue)
.collect(toImmutableList());
}
@VisibleForTesting
static ImmutableListMultimap<String, CodeTransformer> loadAllCodeTransformers() {
ImmutableListMultimap.Builder<String, CodeTransformer> transformers =
ImmutableListMultimap.builder();
for (ResourceInfo resource : getClassPathResources()) {
getRefasterTemplateName(resource)
.ifPresent(
templateName ->
loadCodeTransformer(resource)
.ifPresent(transformer -> transformers.put(templateName, transformer)));
}
return transformers.build();
}
private static ImmutableSet<ResourceInfo> getClassPathResources() {
try {
return ClassPath.from(
Objects.requireNonNullElseGet(
RefasterCheck.class.getClassLoader(), () -> ClassLoader.getSystemClassLoader()))
.getResources();
} catch (IOException e) {
throw new UncheckedIOException("Failed to scan classpath for resources", e);
}
}
private static Optional<String> getRefasterTemplateName(ResourceInfo resource) {
String resourceName = resource.getResourceName();
if (!resourceName.endsWith(REFASTER_TEMPLATE_SUFFIX)) {
return Optional.empty();
}
int lastPathSeparator = resourceName.lastIndexOf('/');
int beginIndex = lastPathSeparator < 0 ? 0 : lastPathSeparator + 1;
int endIndex = resourceName.length() - REFASTER_TEMPLATE_SUFFIX.length();
return Optional.of(resourceName.substring(beginIndex, endIndex));
}
private static Optional<CodeTransformer> loadCodeTransformer(ResourceInfo resource) {
try (InputStream in = resource.url().openStream();
ObjectInputStream ois = new ObjectInputStream(in)) {
return Optional.of((CodeTransformer) ois.readObject());
} catch (NoSuchElementException e) {
/* For some reason we can't load the resource. Skip it. */
// XXX: Should we log this?
return Optional.empty();
} catch (IOException | ClassNotFoundException e) {
throw new IllegalStateException("Can't load `CodeTransformer` from " + resource, e);
}
}
}

View File

@@ -1,19 +1,21 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.isType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.not;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.ProvidesFix;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
@@ -30,11 +32,13 @@ import com.sun.source.tree.Tree;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "RequestMappingAnnotation",
summary = "Make sure all `@RequestMapping` method parameters are annotated",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class RequestMappingAnnotation extends BugChecker implements MethodTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.LIKELY_ERROR,
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION)
public final class RequestMappingAnnotationCheck extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
// XXX: Generalize this logic to fully support Spring meta-annotations, then update the class
@@ -49,11 +53,8 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
isType(ANN_PACKAGE_PREFIX + "PostMapping"),
isType(ANN_PACKAGE_PREFIX + "PutMapping"),
isType(ANN_PACKAGE_PREFIX + "RequestMapping")));
// XXX: Add other parameters as necessary. Also consider whether it makes sense to have WebMVC-
// and WebFlux-specific logic. See
// https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
// and
// https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-arguments.
// XXX: Add other parameters as necessary. See
// https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-arguments.
private static final Matcher<MethodTree> LACKS_PARAMETER_ANNOTATION =
not(
methodHasParameters(
@@ -63,22 +64,14 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
AT_LEAST_ONE,
anyOf(
isType(ANN_PACKAGE_PREFIX + "PathVariable"),
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
isType(ANN_PACKAGE_PREFIX + "RequestParam"))),
isSameType("java.io.InputStream"),
isSameType("java.time.ZoneId"),
isSameType("java.util.Locale"),
isSameType("java.util.TimeZone"),
isSameType("javax.servlet.http.HttpServletRequest"),
isSameType("javax.servlet.http.HttpServletResponse"),
isSameType("org.springframework.http.HttpMethod"),
isSameType("org.springframework.web.context.request.NativeWebRequest"),
isSameType("org.springframework.web.context.request.WebRequest"),
isSameType("org.springframework.web.server.ServerWebExchange"),
isSameType("org.springframework.web.util.UriBuilder"),
isSameType("org.springframework.web.util.UriComponentsBuilder"))));
isSubtypeOf("org.springframework.web.context.request.WebRequest"))));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {

View File

@@ -1,44 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
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.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.BugPattern;
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.sun.source.tree.VariableTree;
/** A {@link BugChecker} which flags {@code @RequestParam} parameters with an unsupported type. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "`@RequestParam` does not support `ImmutableCollection` and `ImmutableMap` subtypes",
linkType = NONE,
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)));
@Override
public Description matchVariable(VariableTree tree, VisitorState state) {
return HAS_UNSUPPORTED_REQUEST_PARAM.matches(tree, state)
? describeMatch(tree)
: Description.NO_MATCH;
}
}

View File

@@ -1,87 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.isType;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
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.MethodTreeMatcher;
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.MultiMatcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
/**
* A {@link BugChecker} which flags methods with Spring's {@code @Scheduled} annotation that lack
* New Relic Agent's {@code @Trace(dispatcher = true)}.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Scheduled operation must start a new New Relic transaction",
linkType = NONE,
severity = ERROR,
tags = LIKELY_ERROR)
public final class ScheduledTransactionTrace extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String TRACE_ANNOTATION_FQCN = "com.newrelic.api.agent.Trace";
private static final Matcher<Tree> IS_SCHEDULED =
hasAnnotation("org.springframework.scheduling.annotation.Scheduled");
private static final MultiMatcher<Tree, AnnotationTree> TRACE_ANNOTATION =
annotations(AT_LEAST_ONE, isType(TRACE_ANNOTATION_FQCN));
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_SCHEDULED.matches(tree, state)) {
return Description.NO_MATCH;
}
ImmutableList<AnnotationTree> traceAnnotations =
TRACE_ANNOTATION.multiMatchResult(tree, state).matchingNodes();
if (traceAnnotations.isEmpty()) {
/* This method completely lacks the `@Trace` annotation; add it. */
return describeMatch(
tree,
SuggestedFix.builder()
.addImport(TRACE_ANNOTATION_FQCN)
.prefixWith(tree, "@Trace(dispatcher = true)")
.build());
}
AnnotationTree traceAnnotation = Iterables.getOnlyElement(traceAnnotations);
if (isCorrectAnnotation(traceAnnotation)) {
return Description.NO_MATCH;
}
/*
* The `@Trace` annotation is present but does not specify `dispatcher = true`. Add or update
* the `dispatcher` annotation element.
*/
return describeMatch(
traceAnnotation,
SuggestedFixes.updateAnnotationArgumentValues(
traceAnnotation, state, "dispatcher", ImmutableList.of("true"))
.build());
}
private static boolean isCorrectAnnotation(AnnotationTree traceAnnotation) {
return Boolean.TRUE.equals(
AnnotationMirrors.getAnnotationValue(
ASTHelpers.getAnnotationMirror(traceAnnotation), "dispatcher")
.getValue());
}
}

View File

@@ -1,15 +1,15 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import com.google.auto.service.AutoService;
import com.google.common.base.Splitter;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
@@ -22,7 +22,6 @@ import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree.Kind;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/** A {@link BugChecker} which flags SLF4J usages that are likely to be in error. */
// XXX: The special-casing of Throwable applies only to SLF4J 1.6.0+; see
@@ -32,11 +31,13 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
// preconditions, ...
@AutoService(BugChecker.class)
@BugPattern(
name = "Slf4jLogStatement",
summary = "Make sure SLF4J log statements contain proper placeholders with matching arguments",
linkType = NONE,
severity = WARNING,
tags = LIKELY_ERROR)
public final class Slf4jLogStatement extends BugChecker implements MethodInvocationTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.LIKELY_ERROR)
public final class Slf4jLogStatementCheck extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> THROWABLE = isSubtypeOf(Throwable.class);
@@ -113,7 +114,7 @@ public final class Slf4jLogStatement extends BugChecker implements MethodInvocat
* replaced at this usage site.
*/
description.addFix(
SuggestedFix.replace(tree, SourceCode.treeToString(tree, state).replace("%s", "{}")));
SuggestedFix.replace(tree, Util.treeToString(tree, state).replace("%s", "{}")));
}
return false;

View File

@@ -1,9 +1,6 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Verify.verify;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
@@ -12,6 +9,9 @@ import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
@@ -25,8 +25,6 @@ import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.Tree.Kind;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.AnnotationAttributeMatcher;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@code @RequestMapping} annotations that can be written more
@@ -34,12 +32,13 @@ import tech.picnic.errorprone.bugpatterns.util.SourceCode;
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "SpringMvcAnnotation",
summary =
"Prefer the conciseness of `@{Get,Put,Post,Delete,Patch}Mapping` over `@RequestMapping`",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class SpringMvcAnnotation extends BugChecker implements AnnotationTreeMatcher {
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class SpringMvcAnnotationCheck extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String ANN_PACKAGE_PREFIX = "org.springframework.web.bind.annotation.";
private static final AnnotationAttributeMatcher ARGUMENT_SELECTOR =
@@ -93,7 +92,7 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
private static String extractMethod(ExpressionTree expr, VisitorState state) {
switch (expr.getKind()) {
case IDENTIFIER:
return SourceCode.treeToString(expr, state);
return Util.treeToString(expr, state);
case MEMBER_SELECT:
return ((MemberSelectTree) expr).getIdentifier().toString();
default:
@@ -106,7 +105,7 @@ public final class SpringMvcAnnotation extends BugChecker implements AnnotationT
String newArguments =
tree.getArguments().stream()
.filter(not(argToRemove::equals))
.map(arg -> SourceCode.treeToString(arg, state))
.map(arg -> Util.treeToString(arg, state))
.collect(joining(", "));
return SuggestedFix.builder()

View File

@@ -1,15 +1,13 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static java.util.Objects.requireNonNull;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.LinkType;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.StandardTags;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
@@ -19,18 +17,15 @@ import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Type;
import java.util.Objects;
import java.util.Optional;
/**
* A {@link BugChecker} which flags methods and constants that can and should be statically
* imported.
*/
/** A {@link BugChecker} which flags methods that can and should be statically imported. */
// XXX: Tricky cases:
// - `org.springframework.http.MediaType` (do except for `ALL`?)
// - `org.springframework.http.HttpStatus` (not always an improvement, and `valueOf` must
// certainly be excluded)
// - `com.google.common.collect.Tables`
@@ -45,29 +40,21 @@ import java.util.Optional;
// - Likely any of `*.{ZERO, ONE, MIX, MAX, MIN_VALUE, MAX_VALUE}`.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Identifier should be statically imported",
linkType = NONE,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class StaticImport extends BugChecker implements MemberSelectTreeMatcher {
name = "StaticImport",
summary = "Method should be statically imported",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class StaticImportCheck extends BugChecker implements MemberSelectTreeMatcher {
private static final long serialVersionUID = 1L;
/**
* Types whose members should be statically imported, unless exempted by {@link
* #STATIC_IMPORT_EXEMPTED_MEMBERS} or {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS}.
*/
@VisibleForTesting
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_TYPES =
static final ImmutableSet<String> STATIC_IMPORT_CANDIDATE_CLASSES =
ImmutableSet.of(
"com.google.common.base.Preconditions",
"com.google.common.base.Predicates",
"com.google.common.base.Verify",
"com.google.common.collect.MoreCollectors",
"com.google.errorprone.BugPattern.LinkType",
"com.google.errorprone.BugPattern.SeverityLevel",
"com.google.errorprone.BugPattern.StandardTags",
"com.google.errorprone.matchers.Matchers",
"com.google.errorprone.refaster.ImportPolicy",
"com.mongodb.client.model.Accumulators",
"com.mongodb.client.model.Aggregates",
"com.mongodb.client.model.Filters",
@@ -76,10 +63,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
"com.mongodb.client.model.Sorts",
"com.mongodb.client.model.Updates",
"java.nio.charset.StandardCharsets",
"java.util.Collections",
"java.util.Comparator",
"java.util.Map.Entry",
"java.util.regex.Pattern",
"java.util.stream.Collectors",
"org.assertj.core.api.Assertions",
"org.assertj.core.api.InstanceOfAssertFactories",
@@ -94,17 +79,14 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
"org.mockito.Answers",
"org.mockito.ArgumentMatchers",
"org.mockito.Mockito",
"org.springframework.boot.test.context.SpringBootTest.WebEnvironment",
"org.springframework.format.annotation.DateTimeFormat.ISO",
"org.springframework.http.HttpHeaders",
"org.springframework.http.HttpMethod",
"org.springframework.http.MediaType",
"org.testng.Assert",
"reactor.function.TupleUtils");
/** Type members that should be statically imported. */
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_MEMBERS =
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_CANDIDATE_METHODS =
ImmutableSetMultimap.<String, String>builder()
.putAll(
"com.google.common.collect.ImmutableListMultimap",
@@ -125,10 +107,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
.put("com.google.common.collect.ImmutableTable", "toImmutableTable")
.put("com.google.common.collect.Sets", "toImmutableEnumSet")
.put("com.google.common.base.Functions", "identity")
.put("java.time.ZoneOffset", "UTC")
.put("java.util.function.Function", "identity")
.put("java.util.function.Predicate", "not")
.put("java.util.UUID", "randomUUID")
.put("org.junit.jupiter.params.provider.Arguments", "arguments")
.putAll(
"java.util.Objects",
@@ -141,57 +121,9 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
.putAll("com.google.common.collect.Comparators", "emptiesFirst", "emptiesLast")
.build();
/**
* Type members that should never be statically imported.
*
* <p>Identifiers listed by {@link #STATIC_IMPORT_EXEMPTED_IDENTIFIERS} should be omitted from
* this collection.
*/
// XXX: Perhaps the set of exempted `java.util.Collections` methods is too strict. For now any
// method name that could be considered "too vague" or could conceivably mean something else in a
// specific context is left out.
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
ImmutableSetMultimap.<String, String>builder()
.put("com.mongodb.client.model.Filters", "empty")
.putAll(
"java.util.Collections",
"addAll",
"copy",
"fill",
"list",
"max",
"min",
"nCopies",
"rotate",
"sort",
"swap")
.putAll("java.util.regex.Pattern", "compile", "matches", "quote")
.put("org.springframework.http.MediaType", "ALL")
.build();
/**
* Identifiers that should never be statically imported.
*
* <p>This should be a superset of the identifiers flagged by {@link
* com.google.errorprone.bugpatterns.BadImport}.
*/
@VisibleForTesting
static final ImmutableSet<String> STATIC_IMPORT_EXEMPTED_IDENTIFIERS =
ImmutableSet.of(
"builder",
"create",
"copyOf",
"from",
"getDefaultInstance",
"INSTANCE",
"newBuilder",
"of",
"valueOf");
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
if (!isCandidateContext(state) || !isCandidate(tree)) {
if (!isCandidate(state)) {
return Description.NO_MATCH;
}
@@ -206,9 +138,10 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
.orElse(Description.NO_MATCH);
}
private static boolean isCandidateContext(VisitorState state) {
private static boolean isCandidate(VisitorState state) {
Tree parentTree =
requireNonNull(state.getPath().getParentPath(), "MemberSelectTree lacks enclosing node")
Objects.requireNonNull(
state.getPath().getParentPath(), "MemberSelectTree lacks enclosing node")
.getLeaf();
switch (parentTree.getKind()) {
case IMPORT:
@@ -221,17 +154,6 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
}
}
private static boolean isCandidate(MemberSelectTree tree) {
String identifier = tree.getIdentifier().toString();
if (STATIC_IMPORT_EXEMPTED_IDENTIFIERS.contains(identifier)) {
return false;
}
Type type = ASTHelpers.getType(tree.getExpression());
return type != null
&& !STATIC_IMPORT_EXEMPTED_MEMBERS.containsEntry(type.toString(), identifier);
}
private static Optional<String> getCandidateSimpleName(StaticImportInfo importInfo) {
String canonicalName = importInfo.canonicalName();
return importInfo
@@ -239,8 +161,8 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
.toJavaUtil()
.filter(
name ->
STATIC_IMPORT_CANDIDATE_TYPES.contains(canonicalName)
|| STATIC_IMPORT_CANDIDATE_MEMBERS.containsEntry(canonicalName, name));
STATIC_IMPORT_CANDIDATE_CLASSES.contains(canonicalName)
|| STATIC_IMPORT_CANDIDATE_METHODS.containsEntry(canonicalName, name));
}
private static Optional<Fix> tryStaticImport(

View File

@@ -1,69 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.enclosingClass;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/** A {@link BugChecker} which flags illegal time-zone related operations. */
@AutoService(BugChecker.class)
@BugPattern(
summary =
"Derive the current time from an existing `Clock` Spring bean, and don't rely on a `Clock`'s time zone",
linkType = NONE,
severity = WARNING,
tags = FRAGILE_CODE)
public final class TimeZoneUsage extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> BANNED_TIME_METHOD =
anyOf(
allOf(
instanceMethod()
.onDescendantOf(Clock.class.getName())
.namedAnyOf("getZone", "withZone"),
not(enclosingClass(isSubtypeOf(Clock.class)))),
staticMethod()
.onClass(Clock.class.getName())
.namedAnyOf(
"system",
"systemDefaultZone",
"systemUTC",
"tickMillis",
"tickMinutes",
"tickSeconds"),
staticMethod()
.onClassAny(
LocalDate.class.getName(),
LocalDateTime.class.getName(),
LocalTime.class.getName())
.named("now"),
staticMethod().onClassAny(Instant.class.getName()).named("now").withNoParameters());
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return BANNED_TIME_METHOD.matches(tree, state)
? buildDescription(tree).build()
: Description.NO_MATCH;
}
}

View File

@@ -1,26 +1,19 @@
package tech.picnic.errorprone.bugpatterns.util;
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.Tree;
/**
* A collection of Error Prone utility methods for dealing with the source code representation of
* AST nodes.
*/
// XXX: Can we locate this code in a better place? Maybe contribute it upstream?
public final class SourceCode {
private SourceCode() {}
final class Util {
private Util() {}
/**
* Returns a string representation of the given {@link Tree}, preferring the original source code
* (if available) over its prettified representation.
*
* @param tree The AST node of interest.
* @param state A {@link VisitorState} describing the context in which the given {@link Tree} is
* found.
* @return A non-{@code null} string.
*/
public static String treeToString(Tree tree, VisitorState state) {
static String treeToString(Tree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}

View File

@@ -1,4 +1,7 @@
/** Picnic Error Prone Contrib checks. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
@CheckReturnValue
@ParametersAreNonnullByDefault
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.annotations.CheckReturnValue;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,129 +0,0 @@
package tech.picnic.errorprone.bugpatterns.util;
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. */
public final class JavaKeywords {
/**
* List of all reserved keywords in the Java language.
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.9">JDK 17 JLS
* section 3.9: Keywords</a>
*/
private static final ImmutableSet<String> RESERVED_KEYWORDS =
ImmutableSet.of(
"_",
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while");
/**
* List of all contextual keywords in the Java language.
*
* @see <a href="https://docs.oracle.com/javase/specs/jls/se17/html/jls-3.html#jls-3.9">JDK 17 JLS
* section 3.9: Keywords</a>
*/
private static final ImmutableSet<String> CONTEXTUAL_KEYWORDS =
ImmutableSet.of(
"exports",
"module",
"non-sealed",
"open",
"opens",
"permits",
"provides",
"record",
"requires",
"sealed",
"to",
"transitive",
"uses",
"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 reserved keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved keyword in the Java language.
*/
public static boolean isReservedKeyword(String str) {
return RESERVED_KEYWORDS.contains(str);
}
/**
* Tells whether the given string is a contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a contextual keyword in the Java language.
*/
public static boolean isContextualKeyword(String str) {
return CONTEXTUAL_KEYWORDS.contains(str);
}
/**
* Tells whether the given string is a reserved or contextual keyword in the Java language.
*
* @param str The string of interest.
* @return {@code true} if the given string is a reserved or contextual keyword in the Java
* language.
*/
public static boolean isKeyword(String str) {
return ALL_KEYWORDS.contains(str);
}
}

View File

@@ -1,4 +0,0 @@
/** Auxiliary utilities for use by Error Prone checks. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
package tech.picnic.errorprone.bugpatterns.util;

View File

@@ -8,22 +8,13 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.math.BigDecimal;
import org.assertj.core.api.AbstractBigDecimalAssert;
import org.assertj.core.api.BigDecimalAssert;
/**
* Refaster templates related to AssertJ assertions over {@link BigDecimal}s.
*
* <p>Note that, contrary to collections of Refaster templates for other {@link
* org.assertj.core.api.NumberAssert} subtypes, these templates do not rewrite to/from {@link
* BigDecimalAssert#isEqualTo(Object)} and {@link BigDecimalAssert#isNotEqualTo(Object)}. This is
* because {@link BigDecimal#equals(Object)} considers not only the numeric value of compared
* instances, but also their scale. As a result various seemingly straightforward transformations
* would actually subtly change the assertion's semantics.
*/
// XXX: If we add a rule which drops unnecessary `L` suffixes from literal longs, then the `0L`/`1L`
// cases below can go.
final class AssertJBigDecimalTemplates {
private AssertJBigDecimalTemplates() {}
static final class AbstractBigDecimalAssertIsEqualByComparingTo {
static final class AbstractBigDecimalAssertIsEqualTo {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return Refaster.anyOf(
@@ -33,11 +24,11 @@ final class AssertJBigDecimalTemplates {
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return bigDecimalAssert.isEqualByComparingTo(n);
return bigDecimalAssert.isEqualTo(n);
}
}
static final class AbstractBigDecimalAssertIsNotEqualByComparingTo {
static final class AbstractBigDecimalAssertIsNotEqualTo {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return Refaster.anyOf(
@@ -47,7 +38,52 @@ final class AssertJBigDecimalTemplates {
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert, BigDecimal n) {
return bigDecimalAssert.isNotEqualByComparingTo(n);
return bigDecimalAssert.isNotEqualTo(n);
}
}
static final class AbstractBigDecimalAssertIsZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isZero(),
bigDecimalAssert.isEqualTo(0L),
bigDecimalAssert.isEqualTo(BigDecimal.ZERO));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(0);
}
}
static final class AbstractBigDecimalAssertIsNotZero {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isNotZero(),
bigDecimalAssert.isNotEqualTo(0L),
bigDecimalAssert.isNotEqualTo(BigDecimal.ZERO));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isNotEqualTo(0);
}
}
static final class AbstractBigDecimalAssertIsOne {
@BeforeTemplate
AbstractBigDecimalAssert<?> before(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return Refaster.anyOf(
bigDecimalAssert.isOne(),
bigDecimalAssert.isEqualTo(1L),
bigDecimalAssert.isEqualTo(BigDecimal.ONE));
}
@AfterTemplate
AbstractBigDecimalAssert<?> after(AbstractBigDecimalAssert<?> bigDecimalAssert) {
return bigDecimalAssert.isEqualTo(1);
}
}
}

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -59,7 +59,7 @@ final class AssertJBooleanTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractBooleanAssert<?> after(boolean b) {
return assertThat(b).isTrue();
}
@@ -88,7 +88,7 @@ final class AssertJBooleanTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractBooleanAssert<?> after(boolean b) {
return assertThat(b).isFalse();
}

View File

@@ -15,7 +15,11 @@ final class AssertJByteTemplates {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> byteAssert, byte n) {
return Refaster.anyOf(
byteAssert.isCloseTo(n, offset((byte) 0)), byteAssert.isCloseTo(n, withPercentage(0)));
byteAssert.isCloseTo(n, offset((byte) 0)),
byteAssert.isCloseTo(Byte.valueOf(n), offset((byte) 0)),
byteAssert.isCloseTo(n, withPercentage(0)),
byteAssert.isCloseTo(Byte.valueOf(n), withPercentage(0)),
byteAssert.isEqualTo(Byte.valueOf(n)));
}
@AfterTemplate
@@ -29,7 +33,10 @@ final class AssertJByteTemplates {
AbstractByteAssert<?> before(AbstractByteAssert<?> byteAssert, byte n) {
return Refaster.anyOf(
byteAssert.isNotCloseTo(n, offset((byte) 0)),
byteAssert.isNotCloseTo(n, withPercentage(0)));
byteAssert.isNotCloseTo(Byte.valueOf(n), offset((byte) 0)),
byteAssert.isNotCloseTo(n, withPercentage(0)),
byteAssert.isNotCloseTo(Byte.valueOf(n), withPercentage(0)),
byteAssert.isNotEqualTo(Byte.valueOf(n)));
}
@AfterTemplate

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -21,7 +21,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(CharSequence charSequence) {
assertThat(charSequence).isEmpty();
}
@@ -36,7 +36,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(CharSequence charSequence) {
return assertThat(charSequence).isNotEmpty();
}
@@ -49,7 +49,7 @@ final class AssertJCharSequenceTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(CharSequence charSequence, int length) {
return assertThat(charSequence).hasSize(length);
}

View File

@@ -36,7 +36,11 @@ final class AssertJDoubleTemplates {
@BeforeTemplate
AbstractDoubleAssert<?> before(AbstractDoubleAssert<?> doubleAssert, double n) {
return Refaster.anyOf(
doubleAssert.isCloseTo(n, offset(0.0)), doubleAssert.isCloseTo(n, withPercentage(0.0)));
doubleAssert.isCloseTo(n, offset(0.0)),
doubleAssert.isCloseTo(Double.valueOf(n), offset(0.0)),
doubleAssert.isCloseTo(n, withPercentage(0.0)),
doubleAssert.isCloseTo(Double.valueOf(n), withPercentage(0.0)),
doubleAssert.isEqualTo(Double.valueOf(n)));
}
@AfterTemplate
@@ -50,7 +54,10 @@ final class AssertJDoubleTemplates {
AbstractDoubleAssert<?> before(AbstractDoubleAssert<?> doubleAssert, double n) {
return Refaster.anyOf(
doubleAssert.isNotCloseTo(n, offset(0.0)),
doubleAssert.isNotCloseTo(n, withPercentage(0.0)));
doubleAssert.isNotCloseTo(Double.valueOf(n), offset(0.0)),
doubleAssert.isNotCloseTo(n, withPercentage(0.0)),
doubleAssert.isNotCloseTo(Double.valueOf(n), withPercentage(0.0)),
doubleAssert.isNotEqualTo(Double.valueOf(n)));
}
@AfterTemplate

View File

@@ -36,7 +36,11 @@ final class AssertJFloatTemplates {
@BeforeTemplate
AbstractFloatAssert<?> before(AbstractFloatAssert<?> floatAssert, float n) {
return Refaster.anyOf(
floatAssert.isCloseTo(n, offset(0F)), floatAssert.isCloseTo(n, withPercentage(0)));
floatAssert.isCloseTo(n, offset(0F)),
floatAssert.isCloseTo(Float.valueOf(n), offset(0F)),
floatAssert.isCloseTo(n, withPercentage(0)),
floatAssert.isCloseTo(Float.valueOf(n), withPercentage(0)),
floatAssert.isEqualTo(Float.valueOf(n)));
}
@AfterTemplate
@@ -49,7 +53,11 @@ final class AssertJFloatTemplates {
@BeforeTemplate
AbstractFloatAssert<?> before(AbstractFloatAssert<?> floatAssert, float n) {
return Refaster.anyOf(
floatAssert.isNotCloseTo(n, offset(0F)), floatAssert.isNotCloseTo(n, withPercentage(0)));
floatAssert.isNotCloseTo(n, offset(0F)),
floatAssert.isNotCloseTo(Float.valueOf(n), offset(0F)),
floatAssert.isNotCloseTo(n, withPercentage(0)),
floatAssert.isNotCloseTo(Float.valueOf(n), withPercentage(0)),
floatAssert.isNotEqualTo(Float.valueOf(n)));
}
@AfterTemplate

View File

@@ -15,7 +15,11 @@ final class AssertJIntegerTemplates {
@BeforeTemplate
AbstractIntegerAssert<?> before(AbstractIntegerAssert<?> intAssert, int n) {
return Refaster.anyOf(
intAssert.isCloseTo(n, offset(0)), intAssert.isCloseTo(n, withPercentage(0)));
intAssert.isCloseTo(n, offset(0)),
intAssert.isCloseTo(Integer.valueOf(n), offset(0)),
intAssert.isCloseTo(n, withPercentage(0)),
intAssert.isCloseTo(Integer.valueOf(n), withPercentage(0)),
intAssert.isEqualTo(Integer.valueOf(n)));
}
@AfterTemplate
@@ -28,7 +32,11 @@ final class AssertJIntegerTemplates {
@BeforeTemplate
AbstractIntegerAssert<?> before(AbstractIntegerAssert<?> intAssert, int n) {
return Refaster.anyOf(
intAssert.isNotCloseTo(n, offset(0)), intAssert.isNotCloseTo(n, withPercentage(0)));
intAssert.isNotCloseTo(n, offset(0)),
intAssert.isNotCloseTo(Integer.valueOf(n), offset(0)),
intAssert.isNotCloseTo(n, withPercentage(0)),
intAssert.isNotCloseTo(Integer.valueOf(n), withPercentage(0)),
intAssert.isNotEqualTo(Integer.valueOf(n)));
}
@AfterTemplate

View File

@@ -15,7 +15,11 @@ final class AssertJLongTemplates {
@BeforeTemplate
AbstractLongAssert<?> before(AbstractLongAssert<?> longAssert, long n) {
return Refaster.anyOf(
longAssert.isCloseTo(n, offset(0L)), longAssert.isCloseTo(n, withPercentage(0)));
longAssert.isCloseTo(n, offset(0L)),
longAssert.isCloseTo(Long.valueOf(n), offset(0L)),
longAssert.isCloseTo(n, withPercentage(0)),
longAssert.isCloseTo(Long.valueOf(n), withPercentage(0)),
longAssert.isEqualTo(Long.valueOf(n)));
}
@AfterTemplate
@@ -28,7 +32,11 @@ final class AssertJLongTemplates {
@BeforeTemplate
AbstractLongAssert<?> before(AbstractLongAssert<?> longAssert, long n) {
return Refaster.anyOf(
longAssert.isNotCloseTo(n, offset(0L)), longAssert.isNotCloseTo(n, withPercentage(0)));
longAssert.isNotCloseTo(n, offset(0L)),
longAssert.isNotCloseTo(Long.valueOf(n), offset(0L)),
longAssert.isNotCloseTo(n, withPercentage(0)),
longAssert.isNotCloseTo(Long.valueOf(n), withPercentage(0)),
longAssert.isNotEqualTo(Long.valueOf(n)));
}
@AfterTemplate

View File

@@ -1,35 +0,0 @@
package tech.picnic.errorprone.refastertemplates;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Map;
import org.assertj.core.api.AbstractMapAssert;
final class AssertJMapTemplates {
private AssertJMapTemplates() {}
static final class AbstractMapAssertContainsExactlyInAnyOrderEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.isEqualTo(map);
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, Map<K, V> map) {
return mapAssert.containsExactlyInAnyOrderEntriesOf(map);
}
}
static final class AbstractMapAssertContainsExactlyEntriesOf<K, V> {
@BeforeTemplate
AbstractMapAssert<?, ?, K, V> before(AbstractMapAssert<?, ?, K, V> mapAssert, K key, V value) {
return mapAssert.containsExactlyInAnyOrderEntriesOf(ImmutableMap.of(key, value));
}
@AfterTemplate
AbstractMapAssert<?, ?, K, V> after(AbstractMapAssert<?, ?, K, V> mapAssert, K key, V value) {
return mapAssert.containsExactlyEntriesOf(ImmutableMap.of(key, value));
}
}
}

View File

@@ -1,12 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
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.math.BigDecimal;
import java.math.BigInteger;
import org.assertj.core.api.AbstractBigDecimalAssert;
@@ -22,7 +18,7 @@ import org.assertj.core.api.NumberAssert;
final class AssertJNumberTemplates {
private AssertJNumberTemplates() {}
static final class NumberAssertIsPositive {
static final class NumberIsPositive {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -73,7 +69,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberAssertIsNotPositive {
static final class NumberIsNotPositive {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -124,7 +120,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberAssertIsNegative {
static final class NumberIsNegative {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -175,7 +171,7 @@ final class AssertJNumberTemplates {
}
}
static final class NumberAssertIsNotNegative {
static final class NumberIsNotNegative {
@BeforeTemplate
AbstractByteAssert<?> before(AbstractByteAssert<?> numberAssert) {
return Refaster.anyOf(
@@ -225,40 +221,4 @@ final class AssertJNumberTemplates {
return numberAssert.isNotNegative();
}
}
static final class AssertThatIsOdd {
@BeforeTemplate
AbstractIntegerAssert<?> before(int number) {
return assertThat(number % 2).isEqualTo(1);
}
@BeforeTemplate
AbstractLongAssert<?> before(long number) {
return assertThat(number % 2).isEqualTo(1);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
NumberAssert<?, ?> after(long number) {
return assertThat(number).isOdd();
}
}
static final class AssertThatIsEven {
@BeforeTemplate
AbstractIntegerAssert<?> before(int number) {
return assertThat(number % 2).isEqualTo(0);
}
@BeforeTemplate
AbstractLongAssert<?> before(long number) {
return assertThat(number % 2).isEqualTo(0);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
NumberAssert<?, ?> after(long number) {
return assertThat(number).isEven();
}
}
}

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -21,7 +21,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object) {
return assertThat(object).isInstanceOf(Refaster.<T>clazz());
}
@@ -34,7 +34,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object) {
return assertThat(object).isNotInstanceOf(Refaster.<T>clazz());
}
@@ -47,7 +47,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object1, T object2) {
return assertThat(object1).isEqualTo(object2);
}
@@ -60,7 +60,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ObjectAssert<S> after(S object1, T object2) {
return assertThat(object1).isNotEqualTo(object2);
}
@@ -73,7 +73,7 @@ final class AssertJObjectTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ObjectAssert<T> after(T object, String str) {
return assertThat(object).hasToString(str);
}

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -26,7 +26,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, T> after(Optional<T> optional) {
return assertThat(optional).get();
}
@@ -53,7 +53,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
OptionalAssert<T> after(Optional<T> optional) {
return assertThat(optional).isPresent();
}
@@ -80,7 +80,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
OptionalAssert<T> after(Optional<T> optional) {
return assertThat(optional).isEmpty();
}
@@ -97,24 +97,12 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractOptionalAssert<?, T> after(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return optionalAssert.hasValue(value);
}
}
static final class AbstractOptionalAssertContainsSame<T> {
@BeforeTemplate
AbstractAssert<?, ?> before(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return Refaster.anyOf(
optionalAssert.get().isSameAs(value), optionalAssert.isPresent().isSameAs(value));
}
@AfterTemplate
AbstractOptionalAssert<?, T> after(AbstractOptionalAssert<?, T> optionalAssert, T value) {
return optionalAssert.containsSame(value);
}
}
static final class AssertThatOptionalHasValueMatching<T> {
@BeforeTemplate
AbstractOptionalAssert<?, T> before(Optional<T> optional, Predicate<? super T> predicate) {
@@ -122,7 +110,7 @@ final class AssertJOptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, T> after(Optional<T> optional, Predicate<? super T> predicate) {
return assertThat(optional).get().matches(predicate);
}

View File

@@ -15,7 +15,11 @@ final class AssertJShortTemplates {
@BeforeTemplate
AbstractShortAssert<?> before(AbstractShortAssert<?> shortAssert, short n) {
return Refaster.anyOf(
shortAssert.isCloseTo(n, offset((short) 0)), shortAssert.isCloseTo(n, withPercentage(0)));
shortAssert.isCloseTo(n, offset((short) 0)),
shortAssert.isCloseTo(Short.valueOf(n), offset((short) 0)),
shortAssert.isCloseTo(n, withPercentage(0)),
shortAssert.isCloseTo(Short.valueOf(n), withPercentage(0)),
shortAssert.isEqualTo(Short.valueOf(n)));
}
@AfterTemplate
@@ -29,7 +33,10 @@ final class AssertJShortTemplates {
AbstractShortAssert<?> before(AbstractShortAssert<?> shortAssert, short n) {
return Refaster.anyOf(
shortAssert.isNotCloseTo(n, offset((short) 0)),
shortAssert.isNotCloseTo(n, withPercentage(0)));
shortAssert.isNotCloseTo(Short.valueOf(n), offset((short) 0)),
shortAssert.isNotCloseTo(n, withPercentage(0)),
shortAssert.isNotCloseTo(Short.valueOf(n), withPercentage(0)),
shortAssert.isNotEqualTo(Short.valueOf(n)));
}
@AfterTemplate

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -31,7 +31,7 @@ final class AssertJStringTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(String string) {
assertThat(string).isEmpty();
}
@@ -56,35 +56,9 @@ final class AssertJStringTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string) {
return assertThat(string).isNotEmpty();
}
}
static final class AssertThatMatches {
@BeforeTemplate
AbstractAssert<?, ?> before(String string, String regex) {
return assertThat(string.matches(regex)).isTrue();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string, String regex) {
return assertThat(string).matches(regex);
}
}
static final class AssertThatDoesNotMatch {
@BeforeTemplate
AbstractAssert<?, ?> before(String string, String regex) {
return assertThat(string.matches(regex)).isFalse();
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractAssert<?, ?> after(String string, String regex) {
return assertThat(string).doesNotMatch(regex);
}
}
}

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableBiMap;
@@ -13,10 +12,10 @@ import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multiset;
import com.google.errorprone.refaster.ImportPolicy;
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.NotMatches;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.ArrayList;
@@ -39,7 +38,6 @@ import java.util.stream.Collector;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractBooleanAssert;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.AbstractComparableAssert;
import org.assertj.core.api.AbstractDoubleAssert;
import org.assertj.core.api.AbstractIntegerAssert;
@@ -53,7 +51,6 @@ import org.assertj.core.api.ObjectEnumerableAssert;
import org.assertj.core.api.OptionalDoubleAssert;
import org.assertj.core.api.OptionalIntAssert;
import org.assertj.core.api.OptionalLongAssert;
import tech.picnic.errorprone.refaster.util.IsArray;
/** Refaster templates related to AssertJ expressions and statements. */
// XXX: Most `AbstractIntegerAssert` rules can also be applied for other primitive types. Generate
@@ -97,6 +94,7 @@ import tech.picnic.errorprone.refaster.util.IsArray;
// XXX: Right now we use and import `Offset.offset` and `Percentage.withPercentage`. Use the AssertJ
// methods instead. (Also in the TestNG migration.)
// ^ Also for `Tuple`!
// XXX: Use `assertThatIllegalArgumentException` and variants.
// XXX: `assertThatCode(x).isInstanceOf(clazz)` -> `assertThatThrownBy(x).isInstanceOf(clazz)`
// (etc.)
// XXX: Look into using Assertions#contentOf(URL url, Charset charset) instead of our own test
@@ -136,7 +134,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
OptionalDoubleAssert after(OptionalDouble optional, double expected) {
return assertThat(optional).hasValue(expected);
}
@@ -155,7 +153,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
OptionalIntAssert after(OptionalInt optional, int expected) {
return assertThat(optional).hasValue(expected);
}
@@ -174,7 +172,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
OptionalLongAssert after(OptionalLong optional, long expected) {
return assertThat(optional).hasValue(expected);
}
@@ -340,14 +338,8 @@ final class AssertJTemplates {
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))));
}
@BeforeTemplate
@SuppressWarnings("unchecked")
ObjectEnumerableAssert<?, S> before2(
ObjectEnumerableAssert<?, S> iterAssert, @NotMatches(IsArray.class) T element) {
return iterAssert.containsExactlyInAnyOrder(element);
ImmutableMultiset.of(element))),
iterAssert.containsExactlyInAnyOrder(element));
}
@AfterTemplate
@@ -357,28 +349,16 @@ final class AssertJTemplates {
}
}
static final class AssertThatSetContainsExactlyOneElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(Set<S> set, T element) {
return assertThat(set).containsOnly(element);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
ObjectEnumerableAssert<?, S> after(Set<S> set, T element) {
return assertThat(set).containsExactly(element);
}
}
static final class ObjectEnumerableContainsOneDistinctElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(ObjectEnumerableAssert<?, S> iterAssert, T element) {
return iterAssert.hasSameElementsAs(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element)));
return Refaster.anyOf(
iterAssert.hasSameElementsAs(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))));
}
@AfterTemplate
@@ -391,12 +371,13 @@ final class AssertJTemplates {
static final class ObjectEnumerableIsSubsetOfOneElement<S, T extends S> {
@BeforeTemplate
ObjectEnumerableAssert<?, S> before(ObjectEnumerableAssert<?, S> iterAssert, T element) {
return iterAssert.isSubsetOf(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element)));
return Refaster.anyOf(
iterAssert.isSubsetOf(
Refaster.anyOf(
ImmutableList.of(element),
Arrays.asList(element),
ImmutableSet.of(element),
ImmutableMultiset.of(element))));
}
@AfterTemplate
@@ -414,8 +395,8 @@ final class AssertJTemplates {
@BeforeTemplate
void before(Iterable<E> iterable) {
Refaster.anyOf(
assertThat(iterable).hasSize(0),
assertThat(iterable.iterator().hasNext()).isFalse(),
assertThat(Iterables.size(iterable)).isEqualTo(0),
assertThat(Iterables.size(iterable)).isEqualTo(0L),
assertThat(Iterables.size(iterable)).isNotPositive());
}
@@ -424,12 +405,13 @@ final class AssertJTemplates {
void before(Collection<E> iterable) {
Refaster.anyOf(
assertThat(iterable.isEmpty()).isTrue(),
assertThat(iterable.size()).isEqualTo(0),
assertThat(iterable.size()).isEqualTo(0L),
assertThat(iterable.size()).isNotPositive());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Collection<E> iterable) {
assertThat(iterable).isEmpty();
}
@@ -453,7 +435,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<E> after(Iterable<E> iterable) {
return assertThat(iterable).isNotEmpty();
}
@@ -471,7 +453,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<E> after(Iterable<E> iterable, int length) {
return assertThat(iterable).hasSize(length);
}
@@ -484,7 +466,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Iterable<S> iterable, T element) {
return assertThat(iterable).containsExactly(element);
}
@@ -501,7 +483,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Iterable<S> iterable, T element) {
return assertThat(iterable).containsExactly(element);
}
@@ -518,7 +500,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(List<S> list1, List<T> list2) {
return assertThat(list1).containsExactlyElementsOf(list2);
}
@@ -530,15 +512,15 @@ final class AssertJTemplates {
static final class AssertThatSetsAreEqual<S, T extends S> {
@BeforeTemplate
AbstractCollectionAssert<?, ?, S, ?> before(Set<S> set1, Set<T> set2) {
IterableAssert<S> before(Set<S> set1, Set<T> set2) {
return Refaster.anyOf(
assertThat(set1).isEqualTo(set2),
assertThat(set1).containsExactlyInAnyOrderElementsOf(set2));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractCollectionAssert<?, ?, S, ?> after(Set<S> set1, Set<T> set2) {
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Set<S> set1, Set<T> set2) {
return assertThat(set1).hasSameElementsAs(set2);
}
}
@@ -549,13 +531,13 @@ final class AssertJTemplates {
static final class AssertThatMultisetsAreEqual<S, T extends S> {
@BeforeTemplate
AbstractCollectionAssert<?, ?, S, ?> before(Multiset<S> multiset1, Multiset<T> multiset2) {
IterableAssert<S> before(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).isEqualTo(multiset2);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractCollectionAssert<?, ?, S, ?> after(Multiset<S> multiset1, Multiset<T> multiset2) {
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).containsExactlyInAnyOrderElementsOf(multiset2);
}
}
@@ -620,8 +602,8 @@ final class AssertJTemplates {
@BeforeTemplate
void before(Map<K, V> map) {
Refaster.anyOf(
assertThat(map).hasSize(0),
assertThat(map.isEmpty()).isTrue(),
assertThat(map.size()).isEqualTo(0),
assertThat(map.size()).isEqualTo(0L),
assertThat(map.size()).isNotPositive());
}
@@ -632,7 +614,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Map<K, V> map) {
assertThat(map).isEmpty();
}
@@ -669,7 +651,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map) {
return assertThat(map).isNotEmpty();
}
@@ -684,7 +666,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, int length) {
return assertThat(map).hasSize(length);
}
@@ -698,7 +680,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map1, Map<K, V> map2) {
return assertThat(map1).hasSameSizeAs(map2);
}
@@ -712,7 +694,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).containsKey(key);
}
@@ -725,7 +707,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key) {
return assertThat(map).doesNotContainKey(key);
}
@@ -738,7 +720,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
MapAssert<K, V> after(Map<K, V> map, K key, V value) {
return assertThat(map).containsEntry(key, value);
}
@@ -763,7 +745,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsAnyElementsOf(iterable);
}
@@ -784,7 +766,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsAnyOf(array);
}
@@ -793,22 +775,19 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsAnyOfVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsAnyOf" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsAnyOf(Refaster.asVarargs(elements));
}
@AfterTemplate
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsAnyOf(elements);
}
@@ -829,7 +808,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsAll(iterable);
}
@@ -850,7 +829,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).contains(array);
}
@@ -859,21 +838,19 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContains" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).contains(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).contains(elements);
}
@@ -888,7 +865,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsExactlyElementsOf(iterable);
}
@@ -903,7 +880,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsExactly(array);
}
@@ -912,14 +889,13 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsExactlyVarargs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactly" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsExactly(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsExactly(elements);
}
@@ -935,13 +911,13 @@ final class AssertJTemplates {
}
@BeforeTemplate
AbstractCollectionAssert<?, ?, T, ?> before2(
IterableAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, Iterable<U> iterable) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrderElementsOf(iterable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsExactlyInAnyOrderElementsOf(iterable);
}
@@ -956,13 +932,13 @@ final class AssertJTemplates {
}
@BeforeTemplate
AbstractCollectionAssert<?, ?, T, ?> before2(
IterableAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, U[] array) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrder(array);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsExactlyInAnyOrder(array);
}
@@ -971,7 +947,6 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsExactlyInAnyOrderVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -979,16 +954,14 @@ final class AssertJTemplates {
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
AbstractCollectionAssert<?, ?, T, ?> before2(
IterableAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
.containsExactlyInAnyOrder(Refaster.asVarargs(elements));
}
@AfterTemplate
@SuppressWarnings("ObjectEnumerableContainsExactlyOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsExactlyInAnyOrder(elements);
}
@@ -1009,7 +982,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsSequence(iterable);
}
@@ -1018,15 +991,13 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsSequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsSequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsSequence(Refaster.asVarargs(elements));
}
@AfterTemplate
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsSequence(elements);
}
@@ -1047,7 +1018,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).containsSubsequence(iterable);
}
@@ -1056,7 +1027,6 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsSubsequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsSubsequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -1064,8 +1034,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@SuppressWarnings("ObjectEnumerableContainsOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsSubsequence(elements);
}
@@ -1086,7 +1055,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).doesNotContainAnyElementsOf(iterable);
}
@@ -1107,7 +1076,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).doesNotContain(array);
}
@@ -1116,21 +1085,19 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamDoesNotContainVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContain" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).doesNotContain(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).doesNotContain(elements);
}
@@ -1151,7 +1118,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).doesNotContainSequence(iterable);
}
@@ -1160,7 +1127,6 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamDoesNotContainSequenceVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamDoesNotContainSequence" /* Varargs converted to array. */)
ListAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
@@ -1168,8 +1134,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@SuppressWarnings("ObjectEnumerableDoesNotContainOneElement" /* Not a true singleton. */)
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).doesNotContainSequence(elements);
}
@@ -1190,7 +1155,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, Iterable<U> iterable) {
return assertThat(stream).hasSameElementsAs(iterable);
}
@@ -1211,7 +1176,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] array) {
return assertThat(stream).containsOnly(array);
}
@@ -1220,21 +1185,19 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamContainsOnlyVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsOnly" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).containsOnly(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).containsOnly(elements);
}
@@ -1267,7 +1230,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, U[] iterable) {
return assertThat(stream).isSubsetOf(iterable);
}
@@ -1276,21 +1239,19 @@ final class AssertJTemplates {
// XXX: This rule assumes the `collector` doesn't completely discard certain values.
static final class AssertThatStreamIsSubsetOfVarArgs<S, T extends S, U extends T> {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
IterableAssert<T> before(
Stream<S> stream, Collector<S, ?, ? extends Iterable<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
}
@BeforeTemplate
@SuppressWarnings("AssertThatStreamIsSubsetOf" /* Varargs converted to array. */)
ListAssert<T> before2(
Stream<S> stream, Collector<S, ?, ? extends List<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector)).isSubsetOf(Refaster.asVarargs(elements));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ListAssert<S> after(Stream<S> stream, @Repeated U elements) {
return assertThat(stream).isSubsetOf(elements);
}
@@ -1309,7 +1270,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Stream<S> stream) {
assertThat(stream).isEmpty();
}
@@ -1328,7 +1289,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Stream<S> stream) {
assertThat(stream).isNotEmpty();
}
@@ -1341,7 +1302,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Stream<T> stream, int size) {
assertThat(stream).hasSize(size);
}
@@ -1358,7 +1319,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Predicate<T> predicate, T object) {
assertThat(predicate).accepts(object);
}
@@ -1371,7 +1332,7 @@ final class AssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Predicate<T> predicate, T object) {
assertThat(predicate).rejects(object);
}
@@ -2212,7 +2173,7 @@ final class AssertJTemplates {
// }
//
// @AfterTemplate
// @UseImportPolicy(STATIC_IMPORT_ALWAYS)
// @UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
// IterableAssert<E> after(Iterable<E> iterable, E expected) {
// return assertThat(iterable).containsExactly(expected);
// }

View File

@@ -1,471 +0,0 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
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.io.IOException;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
/**
* Refaster templates related to AssertJ assertions over expressions that may throw a {@link
* Throwable} subtype.
*
* <p>For reasons of consistency we prefer {@link
* org.assertj.core.api.Assertions#assertThatThrownBy} over static methods for specific exception
* types. Note that only the most common assertion expressions are rewritten here; covering all
* cases would require the implementation of an Error Prone check instead.
*/
final class AssertJThrowingCallableTemplates {
private AssertJThrowingCallableTemplates() {}
static final class AssertThatThrownByIllegalArgumentException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIllegalArgumentException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IllegalArgumentException.class);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining(message);
}
}
static final class AssertThatThrownByIllegalArgumentExceptionHasMessageNotContainingAny {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalArgumentException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, @Repeated CharSequence values) {
return assertThatIllegalArgumentException()
.isThrownBy(throwingCallable)
.withMessageNotContainingAny(values);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, @Repeated CharSequence values) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageNotContainingAny(values);
}
}
static final class AssertThatThrownByIllegalStateException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIllegalStateException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IllegalStateException.class);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining(message);
}
}
static final class AssertThatThrownByIllegalStateExceptionHasMessageNotContaining {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIllegalStateException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIllegalStateException()
.isThrownBy(throwingCallable)
.withMessageNotContaining(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IllegalStateException.class)
.hasMessageNotContaining(message);
}
}
static final class AssertThatThrownByNullPointerException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatNullPointerException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(NullPointerException.class);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatNullPointerException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatNullPointerException()
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownByNullPointerExceptionHasMessageStartingWith {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByNullPointerException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatNullPointerException()
.isThrownBy(throwingCallable)
.withMessageStartingWith(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(NullPointerException.class)
.hasMessageStartingWith(message);
}
}
static final class AssertThatThrownByIOException {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable) {
return assertThatIOException().isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(IOException.class);
}
}
static final class AssertThatThrownByIOExceptionHasMessage {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(ThrowingCallable throwingCallable, String message) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(ThrowingCallable throwingCallable, String message) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IOException.class)
.hasMessage(message);
}
}
static final class AssertThatThrownByIOExceptionHasMessageParameters {
@BeforeTemplate
@SuppressWarnings(
"AssertThatThrownByIOException" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatIOException().isThrownBy(throwingCallable).withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
ThrowingCallable throwingCallable, String message, @Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(IOException.class)
.hasMessage(message, parameters);
}
}
static final class AssertThatThrownBy {
@BeforeTemplate
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType, ThrowingCallable throwingCallable) {
return assertThatExceptionOfType(exceptionType).isThrownBy(throwingCallable);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType, ThrowingCallable throwingCallable) {
return assertThatThrownBy(throwingCallable).isInstanceOf(exceptionType);
}
}
static final class AssertThatThrownByHasMessage {
@BeforeTemplate
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message) {
return assertThatExceptionOfType(exceptionType)
.isThrownBy(throwingCallable)
.withMessage(message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message) {
return assertThatThrownBy(throwingCallable).isInstanceOf(exceptionType).hasMessage(message);
}
}
static final class AssertThatThrownByHasMessageParameters {
@BeforeTemplate
@SuppressWarnings("AssertThatThrownBy" /* Matches strictly more specific expressions. */)
AbstractObjectAssert<?, ?> before(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatExceptionOfType(exceptionType)
.isThrownBy(throwingCallable)
.withMessage(message, parameters);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractObjectAssert<?, ?> after(
Class<? extends Throwable> exceptionType,
ThrowingCallable throwingCallable,
String message,
@Repeated Object parameters) {
return assertThatThrownBy(throwingCallable)
.isInstanceOf(exceptionType)
.hasMessage(message, parameters);
}
}
// XXX: Drop this template in favour of a generic Error Prone check which flags
// `String.format(...)` arguments to a wide range of format methods.
static final class AbstractThrowableAssertHasMessage {
@BeforeTemplate
AbstractThrowableAssert<?, ? extends Throwable> before(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object parameters) {
return abstractThrowableAssert.hasMessage(String.format(message, parameters));
}
@AfterTemplate
AbstractThrowableAssert<?, ? extends Throwable> after(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object parameters) {
return abstractThrowableAssert.hasMessage(message, parameters);
}
}
// XXX: Drop this template in favour of a generic Error Prone check which flags
// `String.format(...)` arguments to a wide range of format methods.
static final class AbstractThrowableAssertWithFailMessage {
@BeforeTemplate
AbstractThrowableAssert<?, ? extends Throwable> before(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object args) {
return abstractThrowableAssert.withFailMessage(String.format(message, args));
}
@AfterTemplate
AbstractThrowableAssert<?, ? extends Throwable> after(
AbstractThrowableAssert<?, ? extends Throwable> abstractThrowableAssert,
String message,
@Repeated Object args) {
return abstractThrowableAssert.withFailMessage(message, args);
}
}
}

View File

@@ -1,18 +1,16 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.toImmutableEnumSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.disjoint;
import static java.util.Objects.checkIndex;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -40,12 +38,12 @@ final class AssortedTemplates {
static final class CheckIndex {
@BeforeTemplate
int before(int index, int size) {
return checkElementIndex(index, size);
return Preconditions.checkElementIndex(index, size);
}
@AfterTemplate
int after(int index, int size) {
return checkIndex(index, size);
return Objects.checkIndex(index, size);
}
}
@@ -64,14 +62,14 @@ final class AssortedTemplates {
}
static final class MapGetOrNull<K, V, L> {
@BeforeTemplate
@Nullable
@BeforeTemplate
V before(Map<K, V> map, L key) {
return map.getOrDefault(key, null);
}
@AfterTemplate
@Nullable
@AfterTemplate
V after(Map<K, V> map, L key) {
return map.get(key);
}
@@ -93,7 +91,7 @@ final class AssortedTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableEnumSet());
}
@@ -109,8 +107,8 @@ final class AssortedTemplates {
Streams.stream(iterator).findAny().orElse(defaultValue));
}
@AfterTemplate
@Nullable
@AfterTemplate
T after(Iterator<T> iterator, T defaultValue) {
return Iterators.getNext(iterator, defaultValue);
}
@@ -163,7 +161,7 @@ final class AssortedTemplates {
@AfterTemplate
boolean after(Set<T> set1, Set<T> set2) {
return disjoint(set1, set2);
return Collections.disjoint(set1, set2);
}
}
@@ -178,15 +176,15 @@ final class AssortedTemplates {
@BeforeTemplate
boolean before(Collection<T> collection1, Collection<T> collection2) {
return Refaster.anyOf(
disjoint(ImmutableSet.copyOf(collection1), collection2),
disjoint(new HashSet<>(collection1), collection2),
disjoint(collection1, ImmutableSet.copyOf(collection2)),
disjoint(collection1, new HashSet<>(collection2)));
Collections.disjoint(ImmutableSet.copyOf(collection1), collection2),
Collections.disjoint(new HashSet<>(collection1), collection2),
Collections.disjoint(collection1, ImmutableSet.copyOf(collection2)),
Collections.disjoint(collection1, new HashSet<>(collection2)));
}
@AfterTemplate
boolean after(Collection<T> collection1, Collection<T> collection2) {
return disjoint(collection1, collection2);
return Collections.disjoint(collection1, collection2);
}
}
@@ -258,6 +256,11 @@ final class AssortedTemplates {
//
// @BeforeTemplate
// void before(Supplier<T> supplier) {
// anyStatement(supplier::get);
// }
//
// @BeforeTemplate
// void before2(Supplier<T> supplier) {
// anyStatement(() -> supplier.get());
// }
//

View File

@@ -15,7 +15,6 @@ import java.util.List;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.IntFunction;
import java.util.stream.Stream;
@@ -130,32 +129,31 @@ final class CollectionTemplates {
}
}
static final class SetRemoveAllCollection<T, S extends T> {
static final class CollectionRemoveAllFromCollectionBlock<T, S extends T> {
@BeforeTemplate
void before(Set<T> removeFrom, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeFrom::remove);
void before(Collection<T> removeTo, Collection<S> elementsToRemove) {
elementsToRemove.forEach(removeTo::remove);
}
@BeforeTemplate
void before2(Set<T> removeFrom, Collection<S> elementsToRemove) {
void before2(Collection<T> removeTo, Collection<S> elementsToRemove) {
for (T element : elementsToRemove) {
removeFrom.remove(element);
removeTo.remove(element);
}
}
// XXX: This method is identical to `before2` except for the loop type. Make Refaster smarter so
// that this is supported out of the box. After doing so, also drop the `S extends T` type
// constraint; ideally this check applies to any `S`.
// that this is supported out of the box.
@BeforeTemplate
void before3(Set<T> removeFrom, Collection<S> elementsToRemove) {
void before3(Collection<T> removeTo, Collection<S> elementsToRemove) {
for (S element : elementsToRemove) {
removeFrom.remove(element);
removeTo.remove(element);
}
}
@AfterTemplate
void after(Set<T> removeFrom, Collection<S> elementsToRemove) {
removeFrom.removeAll(elementsToRemove);
void after(Collection<T> removeTo, Collection<S> elementsToRemove) {
removeTo.removeAll(elementsToRemove);
}
}
@@ -189,7 +187,7 @@ final class CollectionTemplates {
* Don't call {@link ImmutableCollection#asList()} if the result is going to be streamed; stream
* directly.
*/
static final class ImmutableCollectionStream<T> {
static final class ImmutableCollectionAsListToStream<T> {
@BeforeTemplate
Stream<T> before(ImmutableCollection<T> collection) {
return collection.asList().stream();

View File

@@ -1,17 +1,12 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingDouble;
import static java.util.Comparator.comparingInt;
import static java.util.Comparator.comparingLong;
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 com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -34,13 +29,14 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<T> before() {
return Refaster.anyOf(
comparing(Refaster.anyOf(identity(), v -> v)), Comparator.<T>reverseOrder().reversed());
Comparator.comparing(Refaster.anyOf(identity(), v -> v)),
Comparator.<T>reverseOrder().reversed());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return naturalOrder();
return Comparator.naturalOrder();
}
}
@@ -52,7 +48,7 @@ final class ComparatorTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<T> after() {
return reverseOrder();
}
@@ -62,11 +58,11 @@ final class ComparatorTemplates {
// XXX: Drop the `Refaster.anyOf` if/when we decide to rewrite one to the other.
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp) {
return comparing(Refaster.anyOf(identity(), v -> v), cmp);
return Comparator.comparing(Refaster.anyOf(identity(), v -> v), cmp);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<T> after(Comparator<T> cmp) {
return cmp;
}
@@ -76,7 +72,7 @@ final class ComparatorTemplates {
static final class ThenComparing<S, T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<S> before(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(comparing(function));
return cmp.thenComparing(Comparator.comparing(function));
}
@AfterTemplate
@@ -89,11 +85,11 @@ final class ComparatorTemplates {
static final class ThenComparingReversed<S, T extends Comparable<? super T>> {
@BeforeTemplate
Comparator<S> before(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(comparing(function).reversed());
return cmp.thenComparing(Comparator.comparing(function).reversed());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<S> after(Comparator<S> cmp, Function<? super S, ? extends T> function) {
return cmp.thenComparing(function, reverseOrder());
}
@@ -104,7 +100,7 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<S> before(
Comparator<S> cmp, Function<? super S, ? extends T> function, Comparator<? super T> cmp2) {
return cmp.thenComparing(comparing(function, cmp2));
return cmp.thenComparing(Comparator.comparing(function, cmp2));
}
@AfterTemplate
@@ -119,7 +115,7 @@ final class ComparatorTemplates {
@BeforeTemplate
Comparator<S> before(
Comparator<S> cmp, Function<? super S, ? extends T> function, Comparator<? super T> cmp2) {
return cmp.thenComparing(comparing(function, cmp2).reversed());
return cmp.thenComparing(Comparator.comparing(function, cmp2).reversed());
}
@AfterTemplate
@@ -133,7 +129,7 @@ final class ComparatorTemplates {
static final class ThenComparingDouble<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToDoubleFunction<? super T> function) {
return cmp.thenComparing(comparingDouble(function));
return cmp.thenComparing(Comparator.comparingDouble(function));
}
@AfterTemplate
@@ -146,7 +142,7 @@ final class ComparatorTemplates {
static final class ThenComparingInt<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToIntFunction<? super T> function) {
return cmp.thenComparing(comparingInt(function));
return cmp.thenComparing(Comparator.comparingInt(function));
}
@AfterTemplate
@@ -159,7 +155,7 @@ final class ComparatorTemplates {
static final class ThenComparingLong<T> {
@BeforeTemplate
Comparator<T> before(Comparator<T> cmp, ToLongFunction<? super T> function) {
return cmp.thenComparing(comparingLong(function));
return cmp.thenComparing(Comparator.comparingLong(function));
}
@AfterTemplate
@@ -180,9 +176,9 @@ final class ComparatorTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<T> after(Comparator<T> cmp) {
return cmp.thenComparing(naturalOrder());
return cmp.thenComparing(Comparator.naturalOrder());
}
}

View File

@@ -233,7 +233,10 @@ final class DoubleStreamTemplates {
static final class DoubleStreamAllMatch {
@BeforeTemplate
boolean before(DoubleStream stream, DoublePredicate predicate) {
return stream.noneMatch(predicate.negate());
return Refaster.anyOf(
stream.noneMatch(predicate.negate()),
!stream.anyMatch(predicate.negate()),
stream.filter(predicate.negate()).findAny().isEmpty());
}
@AfterTemplate
@@ -248,7 +251,10 @@ final class DoubleStreamTemplates {
@BeforeTemplate
boolean before(DoubleStream stream) {
return stream.noneMatch(e -> !test(e));
return Refaster.anyOf(
stream.noneMatch(e -> !test(e)),
!stream.anyMatch(e -> !test(e)),
stream.filter(e -> !test(e)).findAny().isEmpty());
}
@AfterTemplate

View File

@@ -4,6 +4,7 @@ import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.NoAutoboxing;
import java.util.Objects;
import java.util.function.Predicate;
@@ -11,31 +12,49 @@ import java.util.function.Predicate;
final class EqualityTemplates {
private EqualityTemplates() {}
/** Prefer reference-based quality for enums. */
// Primitive value comparisons are not listed, because Error Prone flags those out of the box.
static final class PrimitiveOrReferenceEquality<T extends Enum<T>> {
/** Prefer primitive/reference-based quality for primitives and enums. */
static final class PrimitiveOrReferenceEquality {
@NoAutoboxing
@BeforeTemplate
boolean before(boolean a, boolean b) {
return Objects.equals(a, b);
}
@NoAutoboxing
@BeforeTemplate
boolean before(long a, long b) {
return Objects.equals(a, b);
}
@NoAutoboxing
@BeforeTemplate
boolean before(double a, double b) {
return Objects.equals(a, b);
}
/**
* Enums can be compared by reference. It is safe to do so even in the face of refactorings,
* because if the type is ever converted to a non-enum, then Error-Prone will complain about any
* remaining reference-based equality checks.
*/
// XXX: This Refaster rule is the topic of https://github.com/google/error-prone/issues/559. We
// work around the issue by selecting the "largest replacements". See the `Refaster` check.
// work around the issue by selecting the "largest replacements". See RefasterCheck.
@BeforeTemplate
boolean before(T a, T b) {
<T extends Enum<T>> boolean before(T a, T b) {
return Refaster.anyOf(a.equals(b), Objects.equals(a, b));
}
@AfterTemplate
@AlsoNegation
boolean after(T a, T b) {
@AfterTemplate
boolean after(boolean a, boolean b) {
return a == b;
}
}
/** Prefer {@link Object#equals(Object)} over the equivalent lambda function. */
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsage` tries to achieve.
// If/when `MethodReferenceUsage` becomes production ready, we should simply drop this check.
// XXX: As it stands, this rule is a special case of what `MethodReferenceUsageCheck` tries to
// achieve. If/when `MethodReferenceUsageCheck` becomes production ready, we should simply drop
// this check.
// XXX: Alternatively, the rule should be replaced with a plugin which also identifies cases where
// the arguments are swapped but simplification is possible anyway, by virtue of `v` being
// non-null.

View File

@@ -2,7 +2,6 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableListMultimap.flatteningToImmutableListMultimap;
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.function.Function.identity;
import com.google.common.collect.ImmutableListMultimap;
@@ -13,6 +12,7 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -56,7 +56,10 @@ final class ImmutableListMultimapTemplates {
static final class EmptyImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableMultimap<K, V> before() {
return Refaster.anyOf(ImmutableListMultimap.<K, V>builder().build(), ImmutableMultimap.of());
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().build(),
ImmutableMultimap.<K, V>builder().build(),
ImmutableMultimap.of());
}
@AfterTemplate
@@ -77,6 +80,7 @@ final class ImmutableListMultimapTemplates {
ImmutableMultimap<K, V> before(K key, V value) {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().put(key, value).build(),
ImmutableMultimap.<K, V>builder().put(key, value).build(),
ImmutableMultimap.of(key, value));
}
@@ -92,11 +96,12 @@ final class ImmutableListMultimapTemplates {
*/
static final class EntryToImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableListMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
ImmutableMultimap<K, V> before(Map.Entry<? extends K, ? extends V> entry) {
return Refaster.anyOf(
ImmutableListMultimap.<K, V>builder().put(entry).build(),
Stream.of(entry)
.collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)));
Stream.of(entry).collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)),
ImmutableMultimap.<K, V>builder().put(entry).build(),
ImmutableMultimap.of(entry.getKey(), entry.getValue()));
}
@AfterTemplate
@@ -113,7 +118,8 @@ final class ImmutableListMultimapTemplates {
ImmutableListMultimap.copyOf(iterable.entries()),
ImmutableListMultimap.<K, V>builder().putAll(iterable).build(),
ImmutableMultimap.copyOf(iterable),
ImmutableMultimap.copyOf(iterable.entries()));
ImmutableMultimap.copyOf(iterable.entries()),
ImmutableMultimap.<K, V>builder().putAll(iterable).build());
}
@BeforeTemplate
@@ -123,6 +129,7 @@ final class ImmutableListMultimapTemplates {
ImmutableListMultimap.<K, V>builder().putAll(iterable).build(),
Streams.stream(iterable)
.collect(toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)),
ImmutableMultimap.<K, V>builder().putAll(iterable).build(),
ImmutableMultimap.copyOf(iterable));
}
@@ -161,7 +168,7 @@ final class ImmutableListMultimapTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableListMultimap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableListMultimap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -272,4 +279,17 @@ final class ImmutableListMultimapTemplates {
return ImmutableListMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableListMultimap}. */
static final class ImmutableListMultimapCopyOfImmutableListMultimap<K, V> {
@BeforeTemplate
ImmutableListMultimap<K, V> before(ImmutableListMultimap<K, V> multimap) {
return ImmutableListMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableListMultimap<K, V> after(ImmutableListMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -2,20 +2,21 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -40,6 +41,37 @@ final class ImmutableListTemplates {
}
}
/** Prefer {@link ImmutableList#of()} over more contrived alternatives. */
static final class EmptyImmutableList<T> {
@BeforeTemplate
ImmutableList<T> before() {
return Refaster.anyOf(
ImmutableList.<T>builder().build(), Stream.<T>empty().collect(toImmutableList()));
}
@AfterTemplate
ImmutableList<T> after() {
return ImmutableList.of();
}
}
/**
* Prefer {@link ImmutableList#of(Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
// XXX: Note that this rewrite rule is incorrect for nullable elements.
static final class SingletonImmutableList<T> {
@BeforeTemplate
List<T> before(T element) {
return Collections.singletonList(element);
}
@AfterTemplate
ImmutableList<T> after(T secondName) {
return ImmutableList.of(secondName);
}
}
/**
* Prefer {@link ImmutableList#copyOf(Iterable)} and variants over more contrived alternatives.
*/
@@ -76,20 +108,40 @@ final class ImmutableListTemplates {
}
}
/** Prefer {@link ImmutableList#toImmutableList()} over less idiomatic alternatives. */
/** Prefer {@link ImmutableList#toImmutableList()} over the more verbose alternative. */
// XXX: Once the code base has been sufficiently cleaned up, we might want to also rewrite
// `Collectors.toList(`), with the caveat that it allows mutation (though this cannot be relied
// upon) as well as nulls. Another option is to explicitly rewrite those variants to
// `Collectors.toSet(ArrayList::new)`.
static final class StreamToImmutableList<T> {
@BeforeTemplate
ImmutableList<T> before(Stream<T> stream) {
return ImmutableList.copyOf(stream.iterator());
return Refaster.anyOf(
ImmutableList.copyOf(stream.iterator()),
ImmutableList.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableList::copyOf)));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableList<T> after(Stream<T> stream) {
return stream.collect(toImmutableList());
}
}
/** Don't call {@link ImmutableList#asList()}; it is a no-op. */
static final class ImmutableListAsList<T> {
@BeforeTemplate
ImmutableList<T> before(ImmutableList<T> list) {
return list.asList();
}
@AfterTemplate
ImmutableList<T> after(ImmutableList<T> list) {
return list;
}
}
/** Prefer {@link ImmutableList#sortedCopyOf(Iterable)} over more contrived alternatives. */
static final class ImmutableListSortedCopyOf<T extends Comparable<? super T>> {
@BeforeTemplate
@@ -143,122 +195,9 @@ final class ImmutableListTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableList<T> after(Stream<T> stream) {
return stream.collect(toImmutableSet()).asList();
}
}
/**
* Prefer {@link ImmutableList#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
// this and similar Refaster templates are replaced with an Error Prone check.
static final class ImmutableListOf<T> {
@BeforeTemplate
List<T> before() {
return Refaster.anyOf(
ImmutableList.<T>builder().build(),
Stream.<T>empty().collect(toImmutableList()),
emptyList(),
List.of());
}
@AfterTemplate
ImmutableList<T> after() {
return ImmutableList.of();
}
}
/**
* Prefer {@link ImmutableList#of(Object)} over more contrived alternatives or alternatives that
* don't communicate the immutability of the resulting list at the type level.
*/
// XXX: Note that the replacement of `Collections#singletonList` is incorrect for nullable
// elements.
static final class ImmutableListOf1<T> {
@BeforeTemplate
List<T> before(T e1) {
return Refaster.anyOf(
ImmutableList.<T>builder().add(e1).build(), singletonList(e1), List.of(e1));
}
@AfterTemplate
ImmutableList<T> after(T e1) {
return ImmutableList.of(e1);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object)} over alternatives that don't communicate the
* immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf2<T> {
@BeforeTemplate
List<T> before(T e1, T e2) {
return List.of(e1, e2);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2) {
return ImmutableList.of(e1, e2);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf3<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3) {
return List.of(e1, e2, e3);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3) {
return ImmutableList.of(e1, e2, e3);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf4<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3, T e4) {
return List.of(e1, e2, e3, e4);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3, T e4) {
return ImmutableList.of(e1, e2, e3, e4);
}
}
/**
* Prefer {@link ImmutableList#of(Object, Object, Object, Object, Object)} over alternatives that
* don't communicate the immutability of the resulting list at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableList.builder()` usages.
static final class ImmutableListOf5<T> {
@BeforeTemplate
List<T> before(T e1, T e2, T e3, T e4, T e5) {
return List.of(e1, e2, e3, e4, e5);
}
@AfterTemplate
ImmutableList<T> after(T e1, T e2, T e3, T e4, T e5) {
return ImmutableList.of(e1, e2, e3, e4, e5);
}
}
}

View File

@@ -1,14 +1,12 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.function.Function.identity;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -16,6 +14,7 @@ import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -41,6 +40,41 @@ final class ImmutableMapTemplates {
}
}
/** Prefer {@link ImmutableMap#of()} over more contrived alternatives. */
static final class EmptyImmutableMap<K, V> {
@BeforeTemplate
ImmutableMap<K, V> before() {
return ImmutableMap.<K, V>builder().build();
}
@AfterTemplate
ImmutableMap<K, V> after() {
return ImmutableMap.of();
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives and
* alternatives that don't communicate the immutability of the resulting map at the type level..
*/
// XXX: One can define variants for more than one key-value pair, but at some point the builder
// actually produces nicer code. So it's not clear we should add Refaster templates for those
// variants.
// XXX: Note that the `singletonMap` rewrite rule is incorrect for nullable elements.
static final class PairToImmutableMap<K, V> {
@BeforeTemplate
Map<K, V> before(K key, V value) {
return Refaster.anyOf(
ImmutableMap.<K, V>builder().put(key, value).build(),
Collections.singletonMap(key, value));
}
@AfterTemplate
ImmutableMap<K, V> after(K key, V value) {
return ImmutableMap.of(key, value);
}
}
/** Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives. */
static final class EntryToImmutableMap<K, V> {
@BeforeTemplate
@@ -144,7 +178,7 @@ final class ImmutableMapTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableMap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableMap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -192,7 +226,7 @@ final class ImmutableMapTemplates {
// XXX: Instead of `Map.Entry::getKey` we could also match `e -> e.getKey()`. But for some
// reason Refaster doesn't handle that case. This doesn't matter if we roll out use of
// `MethodReferenceUsage`. Same observation applies to a lot of other Refaster checks.
// `MethodReferenceUsageCheck`. Same observation applies to a lot of other Refaster checks.
@BeforeTemplate
@SuppressWarnings("NullAway")
ImmutableMap<K, V2> before(Map<K, V1> map) {
@@ -208,108 +242,16 @@ final class ImmutableMapTemplates {
}
}
/**
* Prefer {@link ImmutableMap#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting map at the type level.
*/
static final class ImmutableMapOf<K, V> {
/** Don't unnecessarily copy an {@link ImmutableMap}. */
static final class ImmutableMapCopyOfImmutableMap<K, V> {
@BeforeTemplate
Map<K, V> before() {
return Refaster.anyOf(ImmutableMap.<K, V>builder().build(), emptyMap(), Map.of());
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(map);
}
@AfterTemplate
ImmutableMap<K, V> after() {
return ImmutableMap.of();
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object)} over more contrived alternatives or alternatives
* that don't communicate the immutability of the resulting map at the type level.
*/
// XXX: Note that the replacement of `Collections#singletonMap` is incorrect for nullable
// elements.
static final class ImmutableMapOf1<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1) {
return Refaster.anyOf(
ImmutableMap.<K, V>builder().put(k1, v1).build(), singletonMap(k1, v1), Map.of(k1, v1));
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1) {
return ImmutableMap.of(k1, v1);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting map at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf2<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2) {
return Map.of(k1, v1, k2, v2);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2) {
return ImmutableMap.of(k1, v1, k2, v2);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object)} over
* alternatives that don't communicate the immutability of the resulting map at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf3<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3) {
return Map.of(k1, v1, k2, v2, k3, v3);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object, Object, Object)}
* over alternatives that don't communicate the immutability of the resulting map at the type
* level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf4<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
return Map.of(k1, v1, k2, v2, k3, v3, k4, v4);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4);
}
}
/**
* Prefer {@link ImmutableMap#of(Object, Object, Object, Object, Object, Object, Object, Object,
* Object, Object)} over alternatives that don't communicate the immutability of the resulting map
* at the type level.
*/
// XXX: Also rewrite the `ImmutableMap.builder()` variant?
static final class ImmutableMapOf5<K, V> {
@BeforeTemplate
Map<K, V> before(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
}
@AfterTemplate
ImmutableMap<K, V> after(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
return ImmutableMap.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return map;
}
}

View File

@@ -1,10 +1,12 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -88,13 +90,29 @@ final class ImmutableMultisetTemplates {
static final class StreamToImmutableMultiset<T> {
@BeforeTemplate
ImmutableMultiset<T> before(Stream<T> stream) {
return ImmutableMultiset.copyOf(stream.iterator());
return Refaster.anyOf(
ImmutableMultiset.copyOf(stream.iterator()),
ImmutableMultiset.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableMultiset::copyOf)));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableMultiset<T> after(Stream<T> stream) {
return stream.collect(toImmutableMultiset());
}
}
/** Don't unnecessarily copy an {@link ImmutableMultiset}. */
static final class ImmutableMultisetCopyOfImmutableMultiset<T> {
@BeforeTemplate
ImmutableMultiset<T> before(ImmutableMultiset<T> multiset) {
return ImmutableMultiset.copyOf(multiset);
}
@AfterTemplate
ImmutableMultiset<T> after(ImmutableMultiset<T> multiset) {
return multiset;
}
}
}

View File

@@ -2,7 +2,6 @@ package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSetMultimap.flatteningToImmutableSetMultimap;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ListMultimap;
@@ -11,6 +10,7 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -138,7 +138,7 @@ final class ImmutableSetMultimapTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableSetMultimap<K, V> after(Stream<E> stream) {
return stream.collect(toImmutableSetMultimap(e -> keyFunction(e), e -> valueFunction(e)));
}
@@ -162,7 +162,7 @@ final class ImmutableSetMultimapTemplates {
@AfterTemplate
ImmutableSetMultimap<K, V2> after(Multimap<K, V1> multimap) {
return ImmutableSetMultimap.copyOf(
Multimaps.transformValues(multimap, e -> valueTransformation(e)));
Multimaps.transformValues(multimap, v -> valueTransformation(v)));
}
}
@@ -215,4 +215,17 @@ final class ImmutableSetMultimapTemplates {
return ImmutableSetMultimap.copyOf(Multimaps.transformValues(multimap, transformation));
}
}
/** Don't unnecessarily copy an {@link ImmutableSetMultimap}. */
static final class ImmutableSetMultimapCopyOfImmutableSetMultimap<K, V> {
@BeforeTemplate
ImmutableSetMultimap<K, V> before(ImmutableSetMultimap<K, V> multimap) {
return ImmutableSetMultimap.copyOf(multimap);
}
@AfterTemplate
ImmutableSetMultimap<K, V> after(ImmutableSetMultimap<K, V> multimap) {
return multimap;
}
}
}

View File

@@ -1,19 +1,21 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets.SetView;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Stream;
@@ -37,6 +39,37 @@ final class ImmutableSetTemplates {
}
}
/** Prefer {@link ImmutableSet#of()} over more contrived alternatives. */
static final class EmptyImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before() {
return Refaster.anyOf(
ImmutableSet.<T>builder().build(), Stream.<T>empty().collect(toImmutableSet()));
}
@AfterTemplate
ImmutableSet<T> after() {
return ImmutableSet.of();
}
}
/**
* Prefer {@link ImmutableSet#of(Object)} over alternatives that don't communicate the
* immutability of the resulting set at the type level.
*/
// XXX: Note that this rewrite rule is incorrect for nullable elements.
static final class SingletonImmutableSet<T> {
@BeforeTemplate
Set<T> before(T element) {
return Collections.singleton(element);
}
@AfterTemplate
ImmutableSet<T> after(T element) {
return ImmutableSet.of(element);
}
}
/** Prefer {@link ImmutableSet#copyOf(Iterable)} and variants over more contrived alternatives. */
static final class IterableToImmutableSet<T> {
@BeforeTemplate
@@ -72,20 +105,41 @@ final class ImmutableSetTemplates {
}
/** Prefer {@link ImmutableSet#toImmutableSet()} over less idiomatic alternatives. */
// XXX: Once the code base has been sufficiently cleaned up, we might want to also rewrite
// `Collectors.toSet(`), with the caveat that it allows mutation (though this cannot be relied
// upon) as well as nulls. Another option is to explicitly rewrite those variants to
// `Collectors.toSet(HashSet::new)`.
static final class StreamToImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before(Stream<T> stream) {
return Refaster.anyOf(
ImmutableSet.copyOf(stream.iterator()), stream.distinct().collect(toImmutableSet()));
ImmutableSet.copyOf(stream.iterator()),
ImmutableSet.copyOf(stream::iterator),
stream.distinct().collect(toImmutableSet()),
stream.collect(collectingAndThen(toList(), ImmutableSet::copyOf)),
stream.collect(collectingAndThen(toSet(), ImmutableSet::copyOf)));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableSet());
}
}
/** Don't unnecessarily copy an {@link ImmutableSet}. */
static final class ImmutableSetCopyOfImmutableSet<T> {
@BeforeTemplate
ImmutableSet<T> before(ImmutableSet<T> set) {
return ImmutableSet.copyOf(set);
}
@AfterTemplate
ImmutableSet<T> after(ImmutableSet<T> set) {
return set;
}
}
/** Prefer {@link SetView#immutableCopy()} over the more verbose alternative. */
static final class ImmutableSetCopyOfSetView<T> {
@BeforeTemplate
@@ -98,115 +152,4 @@ final class ImmutableSetTemplates {
return set.immutableCopy();
}
}
/**
* Prefer {@link ImmutableSet#of()} over more contrived alternatives or alternatives that don't
* communicate the immutability of the resulting set at the type level.
*/
// XXX: The `Stream` variant may be too contrived to warrant inclusion. Review its usage if/when
// this and similar Refaster templates are replaced with an Error Prone check.
static final class ImmutableSetOf<T> {
@BeforeTemplate
Set<T> before() {
return Refaster.anyOf(
ImmutableSet.<T>builder().build(),
Stream.<T>empty().collect(toImmutableSet()),
emptySet(),
Set.of());
}
@AfterTemplate
ImmutableSet<T> after() {
return ImmutableSet.of();
}
}
/**
* Prefer {@link ImmutableSet#of(Object)} over more contrived alternatives or alternatives that
* don't communicate the immutability of the resulting set at the type level.
*/
// XXX: Note that the replacement of `Collections#singleton` is incorrect for nullable elements.
static final class ImmutableSetOf1<T> {
@BeforeTemplate
Set<T> before(T e1) {
return Refaster.anyOf(ImmutableSet.<T>builder().add(e1).build(), singleton(e1), Set.of(e1));
}
@AfterTemplate
ImmutableSet<T> after(T e1) {
return ImmutableSet.of(e1);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object)} over alternatives that don't communicate the
* immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf2<T> {
@BeforeTemplate
Set<T> before(T e1, T e2) {
return Set.of(e1, e2);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2) {
return ImmutableSet.of(e1, e2);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object)} over alternatives that don't communicate
* the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf3<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3) {
return Set.of(e1, e2, e3);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3) {
return ImmutableSet.of(e1, e2, e3);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object)} over alternatives that don't
* communicate the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf4<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3, T e4) {
return Set.of(e1, e2, e3, e4);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3, T e4) {
return ImmutableSet.of(e1, e2, e3, e4);
}
}
/**
* Prefer {@link ImmutableSet#of(Object, Object, Object, Object, Object)} over alternatives that
* don't communicate the immutability of the resulting set at the type level.
*/
// XXX: Consider writing an Error Prone check which also flags straightforward
// `ImmutableSet.builder()` usages.
static final class ImmutableSetOf5<T> {
@BeforeTemplate
Set<T> before(T e1, T e2, T e3, T e4, T e5) {
return Set.of(e1, e2, e3, e4, e5);
}
@AfterTemplate
ImmutableSet<T> after(T e1, T e2, T e3, T e4, T e5) {
return ImmutableSet.of(e1, e2, e3, e4, e5);
}
}
}

View File

@@ -1,11 +1,14 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSortedMultiset.toImmutableSortedMultiset;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -90,14 +93,14 @@ final class ImmutableSortedMultisetTemplates {
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedMultiset<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedMultiset<T> before(T[] iterable) {
ImmutableMultiset<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.<T>naturalOrder().add(iterable).build(),
Arrays.stream(iterable).collect(toImmutableSortedMultiset(naturalOrder())));
}
@BeforeTemplate
ImmutableSortedMultiset<T> before(Iterator<T> iterable) {
ImmutableMultiset<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),
@@ -105,7 +108,7 @@ final class ImmutableSortedMultisetTemplates {
}
@BeforeTemplate
ImmutableSortedMultiset<T> before(Iterable<T> iterable) {
ImmutableMultiset<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(naturalOrder(), iterable),
ImmutableSortedMultiset.<T>naturalOrder().addAll(iterable).build(),
@@ -131,11 +134,14 @@ final class ImmutableSortedMultisetTemplates {
static final class StreamToImmutableSortedMultiset<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedMultiset<T> before(Stream<T> stream) {
return ImmutableSortedMultiset.copyOf(stream.iterator());
return Refaster.anyOf(
ImmutableSortedMultiset.copyOf(stream.iterator()),
ImmutableSortedMultiset.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableSortedMultiset::copyOf)));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableSortedMultiset<T> after(Stream<T> stream) {
return stream.collect(toImmutableSortedMultiset(naturalOrder()));
}

View File

@@ -1,11 +1,14 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -88,14 +91,14 @@ final class ImmutableSortedSetTemplates {
// `reverseOrder`.) Worth the hassle?
static final class IterableToImmutableSortedSet<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedSet<T> before(T[] iterable) {
ImmutableSet<T> before(T[] iterable) {
return Refaster.anyOf(
ImmutableSortedSet.<T>naturalOrder().add(iterable).build(),
Arrays.stream(iterable).collect(toImmutableSortedSet(naturalOrder())));
}
@BeforeTemplate
ImmutableSortedSet<T> before(Iterator<T> iterable) {
ImmutableSet<T> before(Iterator<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),
@@ -103,7 +106,7 @@ final class ImmutableSortedSetTemplates {
}
@BeforeTemplate
ImmutableSortedSet<T> before(Iterable<T> iterable) {
ImmutableSet<T> before(Iterable<T> iterable) {
return Refaster.anyOf(
ImmutableSortedSet.copyOf(naturalOrder(), iterable),
ImmutableSortedSet.<T>naturalOrder().addAll(iterable).build(),
@@ -131,11 +134,14 @@ final class ImmutableSortedSetTemplates {
static final class StreamToImmutableSortedSet<T extends Comparable<? super T>> {
@BeforeTemplate
ImmutableSortedSet<T> before(Stream<T> stream) {
return ImmutableSortedSet.copyOf(stream.iterator());
return Refaster.anyOf(
ImmutableSortedSet.copyOf(stream.iterator()),
ImmutableSortedSet.copyOf(stream::iterator),
stream.collect(collectingAndThen(toList(), ImmutableSortedSet::copyOf)));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
ImmutableSortedSet<T> after(Stream<T> stream) {
return stream.collect(toImmutableSortedSet(naturalOrder()));
}

View File

@@ -246,7 +246,10 @@ final class IntStreamTemplates {
static final class IntStreamAllMatch {
@BeforeTemplate
boolean before(IntStream stream, IntPredicate predicate) {
return stream.noneMatch(predicate.negate());
return Refaster.anyOf(
stream.noneMatch(predicate.negate()),
!stream.anyMatch(predicate.negate()),
stream.filter(predicate.negate()).findAny().isEmpty());
}
@AfterTemplate
@@ -261,7 +264,10 @@ final class IntStreamTemplates {
@BeforeTemplate
boolean before(IntStream stream) {
return stream.noneMatch(e -> !test(e));
return Refaster.anyOf(
stream.noneMatch(e -> !test(e)),
!stream.anyMatch(e -> !test(e)),
stream.filter(e -> !test(e)).findAny().isEmpty());
}
@AfterTemplate

View File

@@ -1,8 +1,8 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
@@ -21,7 +21,7 @@ final class JUnitTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Arguments after(@Repeated T objects) {
return arguments(objects);
}

View File

@@ -246,7 +246,10 @@ final class LongStreamTemplates {
static final class LongStreamAllMatch {
@BeforeTemplate
boolean before(LongStream stream, LongPredicate predicate) {
return stream.noneMatch(predicate.negate());
return Refaster.anyOf(
stream.noneMatch(predicate.negate()),
!stream.anyMatch(predicate.negate()),
stream.filter(predicate.negate()).findAny().isEmpty());
}
@AfterTemplate
@@ -261,7 +264,10 @@ final class LongStreamTemplates {
@BeforeTemplate
boolean before(LongStream stream) {
return stream.noneMatch(e -> !test(e));
return Refaster.anyOf(
stream.noneMatch(e -> !test(e)),
!stream.anyMatch(e -> !test(e)),
stream.filter(e -> !test(e)).findAny().isEmpty());
}
@AfterTemplate

View File

@@ -1,12 +1,7 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.comparing;
import static java.util.Comparator.naturalOrder;
import static java.util.Map.Entry.comparingByKey;
import static java.util.Map.Entry.comparingByValue;
import com.google.common.collect.Maps;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -47,13 +42,15 @@ final class MapEntryTemplates {
static final class MapEntryComparingByKey<K extends Comparable<? super K>, V> {
@BeforeTemplate
Comparator<Map.Entry<K, V>> before() {
return Refaster.anyOf(comparing(Map.Entry::getKey), comparingByKey(naturalOrder()));
return Refaster.anyOf(
Comparator.comparing(Map.Entry::getKey),
Map.Entry.comparingByKey(Comparator.naturalOrder()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<Map.Entry<K, V>> after() {
return comparingByKey();
return Map.Entry.comparingByKey();
}
}
@@ -61,13 +58,13 @@ final class MapEntryTemplates {
static final class MapEntryComparingByKeyWithCustomComparator<K, V> {
@BeforeTemplate
Comparator<Map.Entry<K, V>> before(Comparator<? super K> cmp) {
return comparing(Map.Entry::getKey, cmp);
return Comparator.comparing(Map.Entry::getKey, cmp);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<Map.Entry<K, V>> after(Comparator<? super K> cmp) {
return comparingByKey(cmp);
return Map.Entry.comparingByKey(cmp);
}
}
@@ -76,13 +73,15 @@ final class MapEntryTemplates {
static final class MapEntryComparingByValue<K, V extends Comparable<? super V>> {
@BeforeTemplate
Comparator<Map.Entry<K, V>> before() {
return Refaster.anyOf(comparing(Map.Entry::getValue), comparingByValue(naturalOrder()));
return Refaster.anyOf(
Comparator.comparing(Map.Entry::getValue),
Map.Entry.comparingByValue(Comparator.naturalOrder()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<Map.Entry<K, V>> after() {
return comparingByValue();
return Map.Entry.comparingByValue();
}
}
@@ -90,13 +89,13 @@ final class MapEntryTemplates {
static final class MapEntryComparingByValueWithCustomComparator<K, V> {
@BeforeTemplate
Comparator<Map.Entry<K, V>> before(Comparator<? super V> cmp) {
return comparing(Map.Entry::getValue, cmp);
return Comparator.comparing(Map.Entry::getValue, cmp);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Comparator<Map.Entry<K, V>> after(Comparator<? super V> cmp) {
return comparingByValue(cmp);
return Map.Entry.comparingByValue(cmp);
}
}
}

View File

@@ -1,10 +1,10 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -26,7 +26,7 @@ final class MockitoTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
VerificationMode after() {
return never();
}
@@ -43,7 +43,7 @@ final class MockitoTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
T after(T mock) {
return verify(mock);
}

View File

@@ -1,9 +1,7 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Objects.requireNonNullElse;
import com.google.common.base.MoreObjects;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -24,9 +22,9 @@ final class NullTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
T after(T first, T second) {
return requireNonNullElse(first, second);
return Objects.requireNonNullElse(first, second);
}
}

View File

@@ -1,8 +1,7 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -15,18 +14,17 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with {@link Optional}s. */
final class OptionalTemplates {
private OptionalTemplates() {}
static final class OptionalOfNullable<T> {
@BeforeTemplate
// XXX: Refaster should be smart enough to also rewrite occurrences in which there are
// parentheses around the null check, but that's currently not the case. Try to fix that.
@BeforeTemplate
@SuppressWarnings("TernaryOperatorOptionalNegativeFiltering" /* Special case. */)
Optional<T> before(@Nullable T object) {
// XXX: This is a special case of `TernaryOperatorOptionalNegativeFiltering`.
Optional<T> before(T object) {
return object == null ? Optional.empty() : Optional.of(object);
}
@@ -93,6 +91,19 @@ final class OptionalTemplates {
}
}
/** Prefer {@link Optional#stream()} over the Guava alternative. */
static final class OptionalToStream<T> {
@BeforeTemplate
Stream<T> before(Optional<T> optional) {
return Streams.stream(optional);
}
@AfterTemplate
Stream<T> after(Optional<T> optional) {
return optional.stream();
}
}
/**
* Don't use the ternary operator to extract the first element of a possibly-empty {@link
* Iterator} as an {@link Optional}.
@@ -104,7 +115,7 @@ final class OptionalTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Optional<T> after(Iterator<T> it) {
return Streams.stream(it).findFirst();
}
@@ -113,7 +124,7 @@ final class OptionalTemplates {
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Maybe our `Refaster` checker should test `compilesWithFix`?
// Maybe our RefasterCheck should test `compilesWithFix`?
abstract static class TernaryOperatorOptionalPositiveFiltering<T> {
@Placeholder
abstract boolean test(T value);
@@ -133,7 +144,7 @@ final class OptionalTemplates {
/** Prefer {@link Optional#filter(Predicate)} over usage of the ternary operator. */
// XXX: This rule may introduce a compilation error: the `test` expression may reference a
// non-effectively final variable, which is not allowed in the replacement lambda expression.
// Maybe our `Refaster` checker should test `compilesWithFix`?
// Maybe our RefasterCheck should test `compilesWithFix`?
abstract static class TernaryOperatorOptionalNegativeFiltering<T> {
@Placeholder
abstract boolean test(T value);
@@ -166,11 +177,6 @@ final class OptionalTemplates {
}
}
/**
* Prefer {@link Optional#map} over a {@link Optional#flatMap} which wraps the result of a
* transformation in an {@link Optional}; the former operation transforms {@code null} to {@link
* Optional#empty()}.
*/
abstract static class MapToNullable<T, S> {
@Placeholder
abstract S toNullableFunction(@MayOptionallyUse T element);
@@ -239,16 +245,9 @@ final class OptionalTemplates {
}
}
/**
* Within a stream's map operation unconditional {@link Optional#orElseThrow()} calls can be
* avoided.
*
* <p><strong>Warning:</strong> this rewrite rule is not completely behavior preserving. The
* original code throws an exception if the mapping operation does not produce a value, while the
* replacement does not.
*/
// XXX: An alternative approach is to use `.flatMap(Optional::stream)`. That may be a bit longer,
// but yields nicer code. Think about it.
/** Within a stream's map operation unconditional {@link Optional#get()} calls can be avoided. */
// XXX: An alternative approach is to `.flatMap(Optional::stream)`. That may be a bit longer, but
// yield nicer code. Think about it.
abstract static class StreamMapToOptionalGet<T, S> {
@Placeholder
abstract Optional<S> toOptionalFunction(@MayOptionallyUse T element);
@@ -318,7 +317,6 @@ final class OptionalTemplates {
/** Prefer {@link Optional#or(Supplier)} over more verbose alternatives. */
abstract static class OptionalOrOtherOptional<T> {
@BeforeTemplate
@SuppressWarnings("NestedOptionals" /* Auto-fix for the `NestedOptionals` check. */)
Optional<T> before(Optional<T> optional1, Optional<T> optional2) {
// XXX: Note that rewriting the first and third variant will change the code's behavior if
// `optional2` has side-effects.

View File

@@ -1,10 +1,9 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.common.collect.MoreCollectors.toOptional;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.MoreCollectors;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -78,7 +77,7 @@ final class ReactorTemplates {
static final class MonoErrorSupplier<T, E extends Throwable> {
@BeforeTemplate
Mono<T> before(Supplier<E> supplier) {
return Mono.error(() -> supplier.get());
return Refaster.anyOf(Mono.error(supplier::get), Mono.error(() -> supplier.get()));
}
@AfterTemplate
@@ -95,7 +94,7 @@ final class ReactorTemplates {
static final class FluxErrorSupplier<T, E extends Throwable> {
@BeforeTemplate
Flux<T> before(Supplier<E> supplier) {
return Flux.error(() -> supplier.get());
return Refaster.anyOf(Flux.error(supplier::get), Flux.error(() -> supplier.get()));
}
@AfterTemplate
@@ -143,35 +142,6 @@ final class ReactorTemplates {
}
}
/** Prefer {@link Flux#concatMap(Function)} over more contrived alternatives. */
static final class FluxConcatMap<T, S> {
@BeforeTemplate
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
return Refaster.anyOf(flux.flatMap(function, 1), flux.flatMapSequential(function, 1));
}
@AfterTemplate
Flux<S> after(Flux<T> flux, Function<? super T, ? extends Publisher<? extends S>> function) {
return flux.concatMap(function);
}
}
/**
* Prefer {@link Flux#concatMapIterable(Function)} over {@link Flux#flatMapIterable(Function)}, as
* the former has equivalent semantics but a clearer name.
*/
static final class FluxConcatMapIterable<T, S> {
@BeforeTemplate
Flux<S> before(Flux<T> flux, Function<? super T, ? extends Iterable<? extends S>> function) {
return flux.flatMapIterable(function);
}
@AfterTemplate
Flux<S> after(Flux<T> flux, Function<? super T, ? extends Iterable<? extends S>> function) {
return flux.concatMapIterable(function);
}
}
/**
* Don't use {@link Mono#flatMapMany(Function)} to implicitly convert a {@link Mono} to a {@link
* Flux}.
@@ -191,19 +161,6 @@ final class ReactorTemplates {
}
}
/** Prefer {@link Mono#flux()}} over more contrived alternatives. */
static final class MonoFlux<T> {
@BeforeTemplate
Flux<T> before(Mono<T> mono) {
return Flux.concat(mono);
}
@AfterTemplate
Flux<T> after(Mono<T> mono) {
return mono.flux();
}
}
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/
@@ -218,38 +175,12 @@ final class ReactorTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Mono<Optional<T>> after(Mono<T> mono) {
return mono.flux().collect(toOptional());
}
}
/** Prefer {@link Mono#cast(Class)} over {@link Mono#map(Function)} with a cast. */
static final class MonoCast<T, S> {
@BeforeTemplate
Mono<S> before(Mono<T> mono) {
return mono.map(Refaster.<S>clazz()::cast);
}
@AfterTemplate
Mono<S> after(Mono<T> mono) {
return mono.cast(Refaster.<S>clazz());
}
}
/** Prefer {@link Flux#cast(Class)} over {@link Flux#map(Function)} with a cast. */
static final class FluxCast<T, S> {
@BeforeTemplate
Flux<S> before(Flux<T> flux) {
return flux.map(Refaster.<S>clazz()::cast);
}
@AfterTemplate
Flux<S> after(Flux<T> flux) {
return flux.cast(Refaster.<S>clazz());
}
}
/** Prefer {@link PublisherProbe#empty()}} over more verbose alternatives. */
static final class PublisherProbeEmpty<T> {
@BeforeTemplate
@@ -263,32 +194,6 @@ final class ReactorTemplates {
}
}
/** Prefer {@link Mono#as(Function)} when creating a {@link StepVerifier}. */
static final class StepVerifierFromMono<T> {
@BeforeTemplate
StepVerifier.FirstStep<? extends T> before(Mono<T> mono) {
return StepVerifier.create(mono);
}
@AfterTemplate
StepVerifier.FirstStep<? extends T> after(Mono<T> mono) {
return mono.as(StepVerifier::create);
}
}
/** Prefer {@link Flux#as(Function)} when creating a {@link StepVerifier}. */
static final class StepVerifierFromFlux<T> {
@BeforeTemplate
StepVerifier.FirstStep<? extends T> before(Flux<T> flux) {
return StepVerifier.create(flux);
}
@AfterTemplate
StepVerifier.FirstStep<? extends T> after(Flux<T> flux) {
return flux.as(StepVerifier::create);
}
}
/** Don't unnecessarily call {@link StepVerifier.Step#expectNext(Object[])}. */
static final class StepVerifierStepExpectNextEmpty<T> {
@BeforeTemplate
@@ -347,9 +252,7 @@ final class ReactorTemplates {
static final class StepVerifierLastStepVerifyErrorClass<T extends Throwable> {
@BeforeTemplate
Duration before(StepVerifier.LastStep step, Class<T> clazz) {
return Refaster.anyOf(
step.expectError(clazz).verify(),
step.verifyErrorSatisfies(t -> assertThat(t).isInstanceOf(clazz)));
return step.expectError(clazz).verify();
}
@AfterTemplate

View File

@@ -41,9 +41,11 @@ final class RxJava2AdapterTemplates {
@BeforeTemplate
Publisher<T> before(Flowable<T> flowable) {
return Refaster.anyOf(
Flux.from(flowable),
flowable.compose(Flux::from),
flowable.to(Flux::from),
flowable.as(Flux::from),
RxJava2Adapter.flowableToFlux(flowable),
flowable.compose(RxJava2Adapter::flowableToFlux),
flowable.to(RxJava2Adapter::flowableToFlux));
}
@@ -65,6 +67,7 @@ final class RxJava2AdapterTemplates {
Flowable.fromPublisher(flux),
flux.transform(Flowable::fromPublisher),
flux.as(Flowable::fromPublisher),
RxJava2Adapter.fluxToFlowable(flux),
flux.transform(RxJava2Adapter::fluxToFlowable));
}
@@ -137,6 +140,7 @@ final class RxJava2AdapterTemplates {
Flowable.fromPublisher(mono),
mono.transform(Flowable::fromPublisher),
mono.as(Flowable::fromPublisher),
RxJava2Adapter.monoToFlowable(mono),
mono.transform(RxJava2Adapter::monoToFlowable));
}

View File

@@ -1,12 +1,11 @@
package tech.picnic.errorprone.refastertemplates;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import com.google.common.collect.Streams;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -19,31 +18,12 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** Refaster templates related to expressions dealing with {@link Stream}s. */
final class StreamTemplates {
private StreamTemplates() {}
/**
* Prefer {@link Collectors#joining()} over {@link Collectors#joining(CharSequence)} with an empty
* delimiter string.
*/
static final class Joining {
@BeforeTemplate
Collector<CharSequence, ?, String> before() {
return joining("");
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Collector<CharSequence, ?, String> after() {
return joining();
}
}
/** Prefer {@link Stream#empty()} over less clear alternatives. */
static final class EmptyStream<T> {
@BeforeTemplate
@@ -168,8 +148,6 @@ final class StreamTemplates {
*/
// XXX: Consider whether to have a similar rule for `.findAny()`. For parallel streams it
// wouldn't be quite the same....
// XXX: This change is not equivalent for `null`-returning functions, as the original code throws
// an NPE if the first element is `null`, while the latter yields an empty `Optional`.
static final class StreamMapFirst<T, S> {
@BeforeTemplate
Optional<S> before(Stream<T> stream, Function<? super T, S> function) {
@@ -236,7 +214,7 @@ final class StreamTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Optional<T> after(Stream<T> stream) {
return stream.min(naturalOrder());
}
@@ -262,7 +240,7 @@ final class StreamTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
Optional<T> after(Stream<T> stream) {
return stream.max(naturalOrder());
}
@@ -316,7 +294,10 @@ final class StreamTemplates {
static final class StreamAllMatch<T> {
@BeforeTemplate
boolean before(Stream<T> stream, Predicate<? super T> predicate) {
return stream.noneMatch(Refaster.anyOf(not(predicate), predicate.negate()));
return Refaster.anyOf(
stream.noneMatch(Refaster.anyOf(not(predicate), predicate.negate())),
!stream.anyMatch(Refaster.anyOf(not(predicate), predicate.negate())),
stream.filter(Refaster.anyOf(not(predicate), predicate.negate())).findAny().isEmpty());
}
@AfterTemplate
@@ -331,7 +312,10 @@ final class StreamTemplates {
@BeforeTemplate
boolean before(Stream<T> stream) {
return stream.noneMatch(e -> !test(e));
return Refaster.anyOf(
stream.noneMatch(e -> !test(e)),
!stream.anyMatch(e -> !test(e)),
stream.filter(e -> !test(e)).findAny().isEmpty());
}
@AfterTemplate

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refastertemplates;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;
import com.google.common.base.Joiner;
@@ -11,10 +10,10 @@ import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with {@link String}s. */
// XXX: Should we prefer `s -> !s.isEmpty()` or `not(String::isEmpty)`?
@@ -38,7 +37,7 @@ final class StringTemplates {
/** Prefer {@link Strings#isNullOrEmpty(String)} over the more verbose alternative. */
static final class StringIsNullOrEmpty {
@BeforeTemplate
boolean before(@Nullable String str) {
boolean before(String str) {
return str == null || str.isEmpty();
}
@@ -123,7 +122,7 @@ final class StringTemplates {
static final class Utf8EncodedLength {
@BeforeTemplate
int before(String str) {
return str.getBytes(UTF_8).length;
return str.getBytes(StandardCharsets.UTF_8).length;
}
@AfterTemplate

View File

@@ -1,6 +1,5 @@
package tech.picnic.errorprone.refastertemplates;
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.assertThatThrownBy;
import static org.assertj.core.api.Assertions.fail;
@@ -17,7 +16,7 @@ import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.refaster.ImportPolicy;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -82,7 +81,6 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@DoNotCall
void after() {
throw new AssertionError();
}
@@ -96,7 +94,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(String message) {
fail(message);
}
@@ -110,7 +108,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(String message, Throwable throwable) {
fail(message, throwable);
}
@@ -123,7 +121,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(boolean condition) {
assertThat(condition).isTrue();
}
@@ -136,7 +134,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(boolean condition, String message) {
assertThat(condition).withFailMessage(message).isTrue();
}
@@ -149,7 +147,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(boolean condition) {
assertThat(condition).isFalse();
}
@@ -162,7 +160,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(boolean condition, String message) {
assertThat(condition).withFailMessage(message).isFalse();
}
@@ -175,7 +173,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object object) {
assertThat(object).isNull();
}
@@ -188,7 +186,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object object, String message) {
assertThat(object).withFailMessage(message).isNull();
}
@@ -201,7 +199,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object object) {
assertThat(object).isNotNull();
}
@@ -214,7 +212,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object object, String message) {
assertThat(object).withFailMessage(message).isNotNull();
}
@@ -227,7 +225,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isSameAs(expected);
}
@@ -240,7 +238,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isSameAs(expected);
}
@@ -253,7 +251,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isNotSameAs(expected);
}
@@ -266,7 +264,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isNotSameAs(expected);
}
@@ -329,7 +327,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isEqualTo(expected);
}
@@ -392,7 +390,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isEqualTo(expected);
}
@@ -405,7 +403,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(float actual, float expected, float delta) {
assertThat(actual).isCloseTo(expected, offset(delta));
}
@@ -418,7 +416,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(float actual, float expected, float delta, String message) {
assertThat(actual).withFailMessage(message).isCloseTo(expected, offset(delta));
}
@@ -431,7 +429,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(double actual, double expected, double delta) {
assertThat(actual).isCloseTo(expected, offset(delta));
}
@@ -444,7 +442,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(double actual, double expected, double delta, String message) {
assertThat(actual).withFailMessage(message).isCloseTo(expected, offset(delta));
}
@@ -497,7 +495,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object[] actual, Object[] expected) {
assertThat(actual).containsExactly(expected);
}
@@ -550,7 +548,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object[] actual, Object[] expected, String message) {
assertThat(actual).withFailMessage(message).containsExactly(expected);
}
@@ -563,7 +561,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object[] actual, Object[] expected) {
assertThat(actual).containsExactlyInAnyOrder(expected);
}
@@ -576,7 +574,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object[] actual, Object[] expected, String message) {
assertThat(actual).withFailMessage(message).containsExactlyInAnyOrder(expected);
}
@@ -589,7 +587,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Iterator<S> actual, Iterator<T> expected) {
// XXX: This is not `null`-safe.
// XXX: The `ImmutableList.copyOf` should actually *not* be imported statically.
@@ -604,7 +602,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Iterator<S> actual, Iterator<T> expected, String message) {
// XXX: This is not `null`-safe.
// XXX: The `ImmutableList.copyOf` should actually *not* be imported statically.
@@ -629,7 +627,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Iterable<S> actual, Iterable<T> expected) {
assertThat(actual).containsExactlyElementsOf(expected);
}
@@ -647,7 +645,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Iterable<S> actual, Iterable<T> expected, String message) {
assertThat(actual).withFailMessage(message).containsExactlyElementsOf(expected);
}
@@ -660,7 +658,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Set<S> actual, Set<T> expected) {
assertThat(actual).hasSameElementsAs(expected);
}
@@ -673,7 +671,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
<S, T extends S> void after(Set<S> actual, Set<T> expected, String message) {
assertThat(actual).withFailMessage(message).hasSameElementsAs(expected);
}
@@ -741,7 +739,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected) {
assertThat(actual).isNotEqualTo(expected);
}
@@ -809,7 +807,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(Object actual, Object expected, String message) {
assertThat(actual).withFailMessage(message).isNotEqualTo(expected);
}
@@ -822,7 +820,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(float actual, float expected, float delta) {
assertThat(actual).isNotCloseTo(expected, offset(delta));
}
@@ -835,7 +833,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(float actual, float expected, float delta, String message) {
assertThat(actual).withFailMessage(message).isNotCloseTo(expected, offset(delta));
}
@@ -848,7 +846,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(double actual, double expected, double delta) {
assertThat(actual).isNotCloseTo(expected, offset(delta));
}
@@ -861,7 +859,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(double actual, double expected, double delta, String message) {
assertThat(actual).withFailMessage(message).isNotCloseTo(expected, offset(delta));
}
@@ -874,7 +872,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable runnable) {
assertThatThrownBy(runnable);
}
@@ -887,7 +885,7 @@ final class TestNGToAssertJTemplates {
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
void after(ThrowingCallable runnable, Class<T> clazz) {
assertThatThrownBy(runnable).isInstanceOf(clazz);
}

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.refastertemplates;
import static java.time.ZoneOffset.UTC;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
@@ -19,7 +17,6 @@ import java.time.ZoneOffset;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.ChronoZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
/** Refaster templates related to expressions dealing with time. */
@@ -46,45 +43,30 @@ final class TimeTemplates {
static final class UtcConstant {
@BeforeTemplate
ZoneId before() {
// `ZoneId.of("Z")` is not listed, because Error Prone flags it out of the box.
return Refaster.anyOf(
ZoneId.of("GMT"),
ZoneId.of("UTC"),
ZoneId.of("Z"),
ZoneId.of("+0"),
ZoneId.of("-0"),
UTC.normalized(),
ZoneId.from(UTC));
ZoneOffset.UTC.normalized(),
ZoneId.from(ZoneOffset.UTC));
}
@AfterTemplate
ZoneOffset after() {
return UTC;
}
}
/** Prefer {@link Instant#atOffset(ZoneOffset)} over the more verbose alternative. */
static final class InstantAtOffset {
@BeforeTemplate
OffsetDateTime before(Instant instant, ZoneOffset zoneOffset) {
return OffsetDateTime.ofInstant(instant, zoneOffset);
}
@AfterTemplate
OffsetDateTime after(Instant instant, ZoneOffset zoneOffset) {
return instant.atOffset(zoneOffset);
return ZoneOffset.UTC;
}
}
/** Use {@link Clock#systemUTC()} when possible. */
static final class UtcClock {
@BeforeTemplate
@SuppressWarnings("TimeZoneUsage")
Clock before() {
return Clock.system(UTC);
return Clock.system(ZoneOffset.UTC);
}
@AfterTemplate
@SuppressWarnings("TimeZoneUsage")
Clock after() {
return Clock.systemUTC();
}
@@ -114,8 +96,8 @@ final class TimeTemplates {
return a.compareTo(b) < 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(Instant a, Instant b) {
return a.isBefore(b);
}
@@ -131,8 +113,8 @@ final class TimeTemplates {
return a.compareTo(b) > 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(Instant a, Instant b) {
return a.isAfter(b);
}
@@ -180,8 +162,8 @@ final class TimeTemplates {
return a.compareTo(b) < 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoLocalDate a, ChronoLocalDate b) {
return a.isBefore(b);
}
@@ -197,8 +179,8 @@ final class TimeTemplates {
return a.compareTo(b) > 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoLocalDate a, ChronoLocalDate b) {
return a.isAfter(b);
}
@@ -214,8 +196,8 @@ final class TimeTemplates {
return a.compareTo(b) < 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoLocalDateTime<?> a, ChronoLocalDateTime<?> b) {
return a.isBefore(b);
}
@@ -231,8 +213,8 @@ final class TimeTemplates {
return a.compareTo(b) > 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoLocalDateTime<?> a, ChronoLocalDateTime<?> b) {
return a.isAfter(b);
}
@@ -248,8 +230,8 @@ final class TimeTemplates {
return a.compareTo(b) < 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoZonedDateTime<?> a, ChronoZonedDateTime<?> b) {
return a.isBefore(b);
}
@@ -265,8 +247,8 @@ final class TimeTemplates {
return a.compareTo(b) > 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(ChronoZonedDateTime<?> a, ChronoZonedDateTime<?> b) {
return a.isAfter(b);
}
@@ -282,8 +264,8 @@ final class TimeTemplates {
return a.compareTo(b) < 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(OffsetDateTime a, OffsetDateTime b) {
return a.isBefore(b);
}
@@ -299,8 +281,8 @@ final class TimeTemplates {
return a.compareTo(b) > 0;
}
@AfterTemplate
@AlsoNegation
@AfterTemplate
boolean after(OffsetDateTime a, OffsetDateTime b) {
return a.isAfter(b);
}
@@ -326,86 +308,8 @@ final class TimeTemplates {
}
}
/** Prefer {@link Duration#ofDays(long)} over alternative representations. */
static final class DurationOfDays {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.DAYS);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofDays(amount);
}
}
/** Prefer {@link Duration#ofHours(long)} over alternative representations. */
static final class DurationOfHours {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.HOURS);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofHours(amount);
}
}
/** Prefer {@link Duration#ofMillis(long)} over alternative representations. */
static final class DurationOfMillis {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.MILLIS);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofMillis(amount);
}
}
/** Prefer {@link Duration#ofMinutes(long)} over alternative representations. */
static final class DurationOfMinutes {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.MINUTES);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofMinutes(amount);
}
}
/** Prefer {@link Duration#ofNanos(long)} over alternative representations. */
static final class DurationOfNanos {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.NANOS);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofNanos(amount);
}
}
/** Prefer {@link Duration#ofSeconds(long)} over alternative representations. */
static final class DurationOfSeconds {
@BeforeTemplate
Duration before(long amount) {
return Duration.of(amount, ChronoUnit.SECONDS);
}
@AfterTemplate
Duration after(long amount) {
return Duration.ofSeconds(amount);
}
}
/**
* Don't unnecessarily convert to and from milliseconds. (This way nanosecond precision is
* Don't unnecessarily convert two and from milliseconds. (This way nanosecond precision is
* retained.)
*
* <p><strong>Warning:</strong> this rewrite rule increases precision!
@@ -423,7 +327,7 @@ final class TimeTemplates {
}
/**
* Don't unnecessarily convert to and from milliseconds. (This way nanosecond precision is
* Don't unnecessarily convert two and from milliseconds. (This way nanosecond precision is
* retained.)
*
* <p><strong>Warning:</strong> this rewrite rule increases precision!

View File

@@ -1,204 +0,0 @@
package tech.picnic.errorprone.refastertemplates;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.HEAD;
import static org.springframework.http.HttpMethod.OPTIONS;
import static org.springframework.http.HttpMethod.PATCH;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Repeated;
import java.util.function.Function;
import org.springframework.http.HttpMethod;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
import org.springframework.web.reactive.function.client.WebClient.RequestBodyUriSpec;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersUriSpec;
/**
* Refaster templates related to expressions dealing with {@link
* org.springframework.web.reactive.function.client.WebClient} and related types.
*/
final class WebClientTemplates {
private WebClientTemplates() {}
/** Prefer {@link RequestBodySpec#bodyValue(Object)} over more contrived alternatives. */
static final class BodyValue<T> {
@BeforeTemplate
RequestHeadersSpec<?> before(RequestBodySpec requestBodySpec, T value) {
return requestBodySpec.body(fromValue(value));
}
@BeforeTemplate
WebTestClient.RequestHeadersSpec<?> before(
WebTestClient.RequestBodySpec requestBodySpec, T value) {
return requestBodySpec.body(fromValue(value));
}
@AfterTemplate
RequestHeadersSpec<?> after(RequestBodySpec requestBodySpec, T value) {
return requestBodySpec.bodyValue(value);
}
}
/**
* Prefer {@link WebClient#get()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#GET}.
*/
static final class WebClientGet {
@BeforeTemplate
RequestHeadersSpec<?> before(WebClient webClient) {
return webClient.method(GET);
}
@BeforeTemplate
WebTestClient.RequestHeadersSpec<?> before(WebTestClient webClient) {
return webClient.method(GET);
}
@AfterTemplate
RequestHeadersSpec<?> after(WebClient webClient) {
return webClient.get();
}
}
/**
* Prefer {@link WebClient#head()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#HEAD}.
*/
static final class WebClientHead {
@BeforeTemplate
RequestHeadersSpec<?> before(WebClient webClient) {
return webClient.method(HEAD);
}
@BeforeTemplate
WebTestClient.RequestHeadersSpec<?> before(WebTestClient webClient) {
return webClient.method(HEAD);
}
@AfterTemplate
RequestHeadersSpec<?> after(WebClient webClient) {
return webClient.head();
}
}
/**
* Prefer {@link WebClient#options()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#OPTIONS}.
*/
static final class WebClientOptions {
@BeforeTemplate
RequestHeadersSpec<?> before(WebClient webClient) {
return webClient.method(OPTIONS);
}
@BeforeTemplate
WebTestClient.RequestHeadersSpec<?> before(WebTestClient webClient) {
return webClient.method(OPTIONS);
}
@AfterTemplate
RequestHeadersSpec<?> after(WebClient webClient) {
return webClient.options();
}
}
/**
* Prefer {@link WebClient#patch()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#PATCH}.
*/
static final class WebClientPatch {
@BeforeTemplate
RequestBodyUriSpec before(WebClient webClient) {
return webClient.method(PATCH);
}
@BeforeTemplate
WebTestClient.RequestBodyUriSpec before(WebTestClient webClient) {
return webClient.method(PATCH);
}
@AfterTemplate
RequestBodyUriSpec after(WebClient webClient) {
return webClient.patch();
}
}
/**
* Prefer {@link WebClient#post()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#POST}.
*/
static final class WebClientPost {
@BeforeTemplate
RequestBodyUriSpec before(WebClient webClient) {
return webClient.method(POST);
}
@BeforeTemplate
WebTestClient.RequestBodyUriSpec before(WebTestClient webClient) {
return webClient.method(POST);
}
@AfterTemplate
RequestBodyUriSpec after(WebClient webClient) {
return webClient.post();
}
}
/**
* Prefer {@link WebClient#put()} over {@link WebClient#method(HttpMethod)} with {@link
* HttpMethod#PUT}.
*/
static final class WebClientPut {
@BeforeTemplate
RequestBodyUriSpec before(WebClient webClient) {
return webClient.method(PUT);
}
@BeforeTemplate
WebTestClient.RequestBodyUriSpec before(WebTestClient webClient) {
return webClient.method(PUT);
}
@AfterTemplate
RequestBodyUriSpec after(WebClient webClient) {
return webClient.put();
}
}
/** Don't unnecessarily use {@link RequestHeadersUriSpec#uri(Function)}. */
abstract static class RequestHeadersUriSpecUri {
@BeforeTemplate
RequestHeadersSpec<?> before(
RequestHeadersUriSpec<?> requestHeadersUriSpec,
String path,
@Repeated Object uriVariables) {
return requestHeadersUriSpec.uri(
uriBuilder -> uriBuilder.path(path).build(Refaster.asVarargs(uriVariables)));
}
@BeforeTemplate
WebTestClient.RequestHeadersSpec<?> before(
WebTestClient.RequestHeadersUriSpec<?> requestHeadersUriSpec,
String path,
@Repeated Object uriVariables) {
return requestHeadersUriSpec.uri(
uriBuilder -> uriBuilder.path(path).build(Refaster.asVarargs(uriVariables)));
}
@AfterTemplate
RequestHeadersSpec<?> after(
RequestHeadersUriSpec<?> requestHeadersUriSpec,
String path,
@Repeated Object uriVariables) {
return requestHeadersUriSpec.uri(path, Refaster.asVarargs(uriVariables));
}
}
}

View File

@@ -1,4 +1,7 @@
/** Picnic Refaster templates. */
@com.google.errorprone.annotations.CheckReturnValue
@javax.annotation.ParametersAreNonnullByDefault
@CheckReturnValue
@ParametersAreNonnullByDefault
package tech.picnic.errorprone.refastertemplates;
import com.google.errorprone.annotations.CheckReturnValue;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -1,148 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AmbiguousJsonCreatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X",
containsPattern("`JsonCreator.Mode` should be set for single-argument creators"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"Container.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
"import com.fasterxml.jackson.annotation.JsonValue;",
"",
"interface Container {",
" enum A {",
" FOO(1);",
"",
" private final int i;",
"",
" A(int i) {",
" this.i = i;",
" }",
"",
" // BUG: Diagnostic matches: X",
" @JsonCreator",
" public static A of(int i) {",
" return FOO;",
" }",
" }",
"",
" enum B {",
" FOO(1);",
"",
" private final int i;",
"",
" B(int i) {",
" this.i = i;",
" }",
"",
" @JsonCreator(mode = JsonCreator.Mode.DELEGATING)",
" public static B of(int i) {",
" return FOO;",
" }",
" }",
"",
" enum C {",
" FOO(1, \"s\");",
"",
" @JsonValue private final int i;",
" private final String s;",
"",
" C(int i, String s) {",
" this.i = i;",
" this.s = s;",
" }",
"",
" // BUG: Diagnostic matches: X",
" @JsonCreator",
" public static C of(int i) {",
" return FOO;",
" }",
" }",
"",
" enum D {",
" FOO(1, \"s\");",
"",
" private final int i;",
" private final String s;",
"",
" D(int i, String s) {",
" this.i = i;",
" this.s = s;",
" }",
"",
" @JsonCreator",
" public static D of(int i, String s) {",
" return FOO;",
" }",
" }",
"",
" enum E {",
" FOO;",
"",
" // BUG: Diagnostic matches: X",
" @JsonCreator",
" public static E of(String s) {",
" return FOO;",
" }",
" }",
"",
" class F {",
" private final String s;",
"",
" F(String s) {",
" this.s = s;",
" }",
"",
" @JsonCreator",
" public static F of(String s) {",
" return new F(s);",
" }",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
"",
"enum A {",
" FOO;",
"",
" @JsonCreator",
" public static A of(String s) {",
" return FOO;",
" }",
"}")
.addOutputLines(
"out/A.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
"",
"enum A {",
" FOO;",
"",
" @JsonCreator(mode = JsonCreator.Mode.DELEGATING)",
" public static A of(String s) {",
" return FOO;",
" }",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.bugpatterns.util;
package tech.picnic.errorprone.bugpatterns;
import static org.assertj.core.api.Assertions.assertThat;
@@ -6,16 +6,16 @@ import com.google.common.collect.ImmutableList;
import java.util.Optional;
import org.junit.jupiter.api.Test;
final class AnnotationAttributeMatcherTest {
public final class AnnotationAttributeMatcherTest {
@Test
void withoutListings() {
public void testWithoutListings() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(Optional.empty(), ImmutableList.of());
assertThat(matcher.matches("foo", "bar")).isTrue();
}
@Test
void withSingleFullAnnotationWhitelist() {
public void testWithSingleFullAnnotationWhitelist() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(Optional.of(ImmutableList.of("foo")), ImmutableList.of());
assertThat(matcher.matches("foo", "bar")).isTrue();
@@ -24,7 +24,7 @@ final class AnnotationAttributeMatcherTest {
}
@Test
void withSingleAnnotationAttributeWhitelist() {
public void testWithSingleAnnotationAttributeWhitelist() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(
Optional.of(ImmutableList.of("foo#bar")), ImmutableList.of());
@@ -34,7 +34,7 @@ final class AnnotationAttributeMatcherTest {
}
@Test
void withSingleFullAnnotationBlacklist() {
public void testWithSingleFullAnnotationBlacklist() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(Optional.empty(), ImmutableList.of("foo"));
assertThat(matcher.matches("foo", "bar")).isFalse();
@@ -43,7 +43,7 @@ final class AnnotationAttributeMatcherTest {
}
@Test
void withSingleAnnotationAttributeBlacklist() {
public void testWithSingleAnnotationAttributeBlacklist() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(Optional.empty(), ImmutableList.of("foo#bar"));
assertThat(matcher.matches("foo", "bar")).isFalse();
@@ -52,7 +52,7 @@ final class AnnotationAttributeMatcherTest {
}
@Test
void withComplicatedConfiguration() {
public void testWithComplicatedConfiguration() {
AnnotationAttributeMatcher matcher =
AnnotationAttributeMatcher.create(
Optional.of(ImmutableList.of("foo", "bar", "baz", "baz#1", "baz#2", "quux#1")),

View File

@@ -1,64 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AssertJIsNullTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(1);",
" // BUG: Diagnostic contains:",
" assertThat(1).isEqualTo(null);",
" // BUG: Diagnostic contains:",
" assertThat(\"foo\").isEqualTo(null);",
" isEqualTo(null);",
" }",
"",
" private boolean isEqualTo(Object value) {",
" return value.equals(\"bar\");",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isEqualTo(null);",
" assertThat(\"foo\").isEqualTo(null);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"class A {",
" void m() {",
" assertThat(1).isNull();",
" assertThat(\"foo\").isNull();",
" }",
"}")
.doTest(TEXT_MATCH);
}
}

View File

@@ -5,14 +5,14 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AutowiredConstructorTest {
public final class AutowiredConstructorCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass());
CompilationTestHelper.newInstance(AutowiredConstructorCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(new AutowiredConstructorCheck(), getClass());
@Test
void identification() {
public void testIdentification() {
compilationTestHelper
.addSourceLines(
"Container.java",
@@ -27,34 +27,27 @@ final class AutowiredConstructorTest {
" }",
"",
" class B {",
" @Autowired",
" void setProperty(Object o) {}",
" @Autowired void setProperty(Object o) {}",
" }",
"",
" class C {",
" // BUG: Diagnostic contains:",
" @Autowired",
" C() {}",
" @Autowired C() {}",
" }",
"",
" class D {",
" // BUG: Diagnostic contains:",
" @Autowired",
" D(String x) {}",
" @Autowired D(String x) {}",
" }",
"",
" class E {",
" @Autowired",
" E() {}",
"",
" @Autowired E() {}",
" E(String x) {}",
" }",
"",
" class F {",
" F() {}",
"",
" @Autowired",
" F(String x) {}",
" @Autowired F(String x) {}",
" }",
"",
" class G {",
@@ -62,15 +55,14 @@ final class AutowiredConstructorTest {
" }",
"",
" class H {",
" @SafeVarargs",
" H(List<String>... lists) {}",
" @SafeVarargs H(List<String>... lists) {}",
" }",
"}")
.doTest();
}
@Test
void replacement() {
public void testReplacement() {
refactoringTestHelper
.addInputLines(
"in/Container.java",
@@ -78,14 +70,11 @@ final class AutowiredConstructorTest {
"",
"interface Container {",
" class A {",
" @Autowired",
" @Deprecated",
" A() {}",
" @Autowired @Deprecated A() {}",
" }",
"",
" class B {",
" @Autowired",
" B(String x) {}",
" @Autowired B(String x) {}",
" }",
"}")
.addOutputLines(
@@ -94,13 +83,10 @@ final class AutowiredConstructorTest {
"",
"interface Container {",
" class A {",
"",
" @Deprecated",
" A() {}",
" @Deprecated A() {}",
" }",
"",
" class B {",
"",
" B(String x) {}",
" }",
"}")

View File

@@ -0,0 +1,156 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
public final class CanonicalAnnotationSyntaxCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntaxCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(new CanonicalAnnotationSyntaxCheck(), getClass());
@Test
public void testIdentification() {
compilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" int[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo A minimal1();",
" @A.Foo A minimal2();",
" @Foo A minimal3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo() A functional1();",
" // BUG: Diagnostic contains:",
" @A.Foo() A functional2();",
" // BUG: Diagnostic contains:",
" @Foo() A functional3();",
"",
" @pkg.A.Foo(1) A simple1();",
" @A.Foo(1) A simple2();",
" @Foo(1) A simple3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1}) A singleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1}) A singleton2();",
" // BUG: Diagnostic contains:",
" @Foo({1}) A singleton3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value = 1) A verbose1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value = 1) A verbose2();",
" // BUG: Diagnostic contains:",
" @Foo(value = 1) A verbose3();",
"",
" @pkg.A.Foo(value2 = 2) A custom1();",
" @A.Foo(value2 = 2) A custom2();",
" @Foo(value2 = 2) A custom3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value2 = {2}) A customSingleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value2 = {2}) A customSingleton2();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {2}) A customSingleton3();",
"",
" @pkg.A.Foo(value2 = {2, 2}) A customPair1();",
" @A.Foo(value2 = {2, 2}) A customPair2();",
" @Foo(value2 = {2, 2}) A customPair3();",
"",
" @pkg.A.Foo(value = 1, value2 = 2) A extended1();",
" @A.Foo(value = 1, value2 = 2) A extended2();",
" @Foo(value = 1, value2 = 2) A extended3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1, 1,}) A trailingComma1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1, 1,}) A trailingComma2();",
" // BUG: Diagnostic contains:",
" @Foo({1, 1,}) A trailingComma3();",
"}")
.doTest();
}
@Test
public void testReplacement() {
refactoringTestHelper
.addInputLines(
"in/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo() A functional1();",
" @A.Foo() A functional2();",
" @Foo() A functional3();",
"",
" @pkg.A.Foo(value = \"foo\") A verbose1();",
" @A.Foo(value = \"a'b\") A verbose2();",
" @Foo(value = \"a\" + \"\\nb\") A verbose3();",
"",
" @pkg.A.Foo(value = {\"foo\"}) A moreVerbose1();",
" @A.Foo(value = {\"a'b\"}) A moreVerbose2();",
" @Foo(value = {\"a\" + \"\\nb\"}) A moreVerbose3();",
"",
" @pkg.A.Foo(value = {\"foo\", \"bar\"}, value2 = {2}) A extended1();",
" @A.Foo(value = {\"a'b\", \"c'd\"}, value2 = {2}) A extended2();",
" @Foo(value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"}, value2 = {2}) A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\",}) A trailingComma1();",
" @A.Foo({\"a'b\", \"c'd\",}) A trailingComma2();",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\",}) A trailingComma3();",
"}")
.addOutputLines(
"out/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo A functional1();",
" @A.Foo A functional2();",
" @Foo A functional3();",
"",
" @pkg.A.Foo(\"foo\") A verbose1();",
" @A.Foo(\"a'b\") A verbose2();",
" @Foo(\"a\" + \"\\nb\") A verbose3();",
"",
" @pkg.A.Foo(\"foo\") A moreVerbose1();",
" @A.Foo(\"a'b\") A moreVerbose2();",
" @Foo(\"a\" + \"\\nb\") A moreVerbose3();",
"",
" @pkg.A.Foo(value = {\"foo\", \"bar\"}, value2 = 2) A extended1();",
" @A.Foo(value = {\"a'b\", \"c'd\"}, value2 = 2) A extended2();",
" @Foo(value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"}, value2 = 2) A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\"}) A trailingComma1();",
" @A.Foo({\"a'b\", \"c'd\"}) A trailingComma2();",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\"}) A trailingComma3();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,274 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CanonicalAnnotationSyntaxTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" int[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo",
" A minimal1();",
"",
" @A.Foo",
" A minimal2();",
"",
" @Foo",
" A minimal3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo()",
" A functional1();",
" // BUG: Diagnostic contains:",
" @A.Foo()",
" A functional2();",
" // BUG: Diagnostic contains:",
" @Foo()",
" A functional3();",
"",
" @pkg.A.Foo(1)",
" A simple1();",
"",
" @A.Foo(1)",
" A simple2();",
"",
" @Foo(1)",
" A simple3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({1})",
" A singleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo({1})",
" A singleton2();",
" // BUG: Diagnostic contains:",
" @Foo({1})",
" A singleton3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value = 1)",
" A verbose1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value = 1)",
" A verbose2();",
" // BUG: Diagnostic contains:",
" @Foo(value = 1)",
" A verbose3();",
"",
" @pkg.A.Foo(value2 = 2)",
" A custom1();",
"",
" @A.Foo(value2 = 2)",
" A custom2();",
"",
" @Foo(value2 = 2)",
" A custom3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo(value2 = {2})",
" A customSingleton1();",
" // BUG: Diagnostic contains:",
" @A.Foo(value2 = {2})",
" A customSingleton2();",
" // BUG: Diagnostic contains:",
" @Foo(value2 = {2})",
" A customSingleton3();",
"",
" @pkg.A.Foo(value2 = {2, 2})",
" A customPair1();",
"",
" @A.Foo(value2 = {2, 2})",
" A customPair2();",
"",
" @Foo(value2 = {2, 2})",
" A customPair3();",
"",
" @pkg.A.Foo(value = 1, value2 = 2)",
" A extended1();",
"",
" @A.Foo(value = 1, value2 = 2)",
" A extended2();",
"",
" @Foo(value = 1, value2 = 2)",
" A extended3();",
"",
" // BUG: Diagnostic contains:",
" @pkg.A.Foo({",
" 1, 1,",
" })",
" A trailingComma1();",
" // BUG: Diagnostic contains:",
" @A.Foo({",
" 1, 1,",
" })",
" A trailingComma2();",
" // BUG: Diagnostic contains:",
" @Foo({",
" 1, 1,",
" })",
" A trailingComma3();",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo()",
" A functional1();",
"",
" @A.Foo()",
" A functional2();",
"",
" @Foo()",
" A functional3();",
"",
" @pkg.A.Foo(value = \"foo\")",
" A verbose1();",
"",
" @A.Foo(value = \"a'b\")",
" A verbose2();",
"",
" @Foo(value = \"a\" + \"\\nb\")",
" A verbose3();",
"",
" @pkg.A.Foo(value = {\"foo\"})",
" A moreVerbose1();",
"",
" @A.Foo(value = {\"a'b\"})",
" A moreVerbose2();",
"",
" @Foo(value = {\"a\" + \"\\nb\"})",
" A moreVerbose3();",
"",
" @pkg.A.Foo(",
" value = {\"foo\", \"bar\"},",
" value2 = {2})",
" A extended1();",
"",
" @A.Foo(",
" value = {\"a'b\", \"c'd\"},",
" value2 = {2})",
" A extended2();",
"",
" @Foo(",
" value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"},",
" value2 = {2})",
" A extended3();",
"",
" @pkg.A.Foo({",
" \"foo\", \"bar\",",
" })",
" A trailingComma1();",
"",
" @A.Foo({",
" \"a'b\", \"c'd\",",
" })",
" A trailingComma2();",
"",
" @Foo({",
" \"a\" + \"\\nb\",",
" \"c\" + \"\\nd\",",
" })",
" A trailingComma3();",
"}")
.addOutputLines(
"out/pkg/A.java",
"package pkg;",
"",
"import pkg.A.Foo;",
"",
"interface A {",
" @interface Foo {",
" String[] value() default {};",
"",
" int[] value2() default {};",
" }",
"",
" @pkg.A.Foo",
" A functional1();",
"",
" @A.Foo",
" A functional2();",
"",
" @Foo",
" A functional3();",
"",
" @pkg.A.Foo(\"foo\")",
" A verbose1();",
"",
" @A.Foo(\"a'b\")",
" A verbose2();",
"",
" @Foo(\"a\" + \"\\nb\")",
" A verbose3();",
"",
" @pkg.A.Foo(\"foo\")",
" A moreVerbose1();",
"",
" @A.Foo(\"a'b\")",
" A moreVerbose2();",
"",
" @Foo(\"a\" + \"\\nb\")",
" A moreVerbose3();",
"",
" @pkg.A.Foo(",
" value = {\"foo\", \"bar\"},",
" value2 = 2)",
" A extended1();",
"",
" @A.Foo(",
" value = {\"a'b\", \"c'd\"},",
" value2 = 2)",
" A extended2();",
"",
" @Foo(",
" value = {\"a\" + \"\\nb\", \"c\" + \"\\nd\"},",
" value2 = 2)",
" A extended3();",
"",
" @pkg.A.Foo({\"foo\", \"bar\"})",
" A trailingComma1();",
"",
" @A.Foo({\"a'b\", \"c'd\"})",
" A trailingComma2();",
"",
" @Foo({\"a\" + \"\\nb\", \"c\" + \"\\nd\"})",
" A trailingComma3();",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,200 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CollectorMutabilityTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CollectorMutability.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.util.stream.Collectors.toCollection;",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.ArrayList;",
"import java.util.HashMap;",
"import java.util.HashSet;",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic contains:",
" Flux.just(1).collect(Collectors.toList());",
" // BUG: Diagnostic contains:",
" Flux.just(2).collect(toList());",
" Flux.just(3).collect(toImmutableList());",
" Flux.just(4).collect(toCollection(ArrayList::new));",
"",
" // BUG: Diagnostic contains:",
" Flux.just(\"foo\").collect(Collectors.toMap(String::getBytes, String::length));",
" // BUG: Diagnostic contains:",
" Flux.just(\"bar\").collect(toMap(String::getBytes, String::length));",
" Flux.just(\"baz\").collect(toImmutableMap(String::getBytes, String::length));",
" // BUG: Diagnostic contains:",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> a));",
" Flux.just(\"quux\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> a));",
" Flux.just(\"quuz\").collect(toMap(String::getBytes, String::length, (a, b) -> a, HashMap::new));",
"",
" // BUG: Diagnostic contains:",
" Stream.of(1).collect(Collectors.toSet());",
" // BUG: Diagnostic contains:",
" Stream.of(2).collect(toSet());",
" Stream.of(3).collect(toImmutableSet());",
" Stream.of(4).collect(toCollection(HashSet::new));",
"",
" Flux.just(\"foo\").collect(Collectors.joining());",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(Collectors.toList());",
" Flux.just(2).collect(toList());",
"",
" Stream.of(\"foo\").collect(Collectors.toMap(String::getBytes, String::length));",
" Stream.of(\"bar\").collect(toMap(String::getBytes, String::length));",
" Flux.just(\"baz\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b));",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b));",
"",
" Stream.of(1).collect(Collectors.toSet());",
" Stream.of(2).collect(toSet());",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toImmutableList());",
" Flux.just(2).collect(toImmutableList());",
"",
" Stream.of(\"foo\").collect(toImmutableMap(String::getBytes, String::length));",
" Stream.of(\"bar\").collect(toImmutableMap(String::getBytes, String::length));",
" Flux.just(\"baz\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));",
" Flux.just(\"qux\").collect(toImmutableMap(String::getBytes, String::length, (a, b) -> b));",
"",
" Stream.of(1).collect(toImmutableSet());",
" Stream.of(2).collect(toImmutableSet());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
.setFixChooser(SECOND)
.addInputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(Collectors.toList());",
" Flux.just(2).collect(toList());",
"",
" Stream.of(\"foo\").collect(Collectors.toMap(String::getBytes, String::length));",
" Stream.of(\"bar\").collect(toMap(String::getBytes, String::length));",
" Flux.just(\"baz\").collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b));",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b));",
"",
" Stream.of(1).collect(Collectors.toSet());",
" Stream.of(2).collect(toSet());",
" }",
"}")
.addOutputLines(
"A.java",
"import static java.util.stream.Collectors.toCollection;",
"import static java.util.stream.Collectors.toList;",
"import static java.util.stream.Collectors.toMap;",
"import static java.util.stream.Collectors.toSet;",
"",
"import java.util.ArrayList;",
"import java.util.HashMap;",
"import java.util.HashSet;",
"import java.util.stream.Collectors;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toCollection(ArrayList::new));",
" Flux.just(2).collect(toCollection(ArrayList::new));",
"",
" Stream.of(\"foo\")",
" .collect(",
" Collectors.toMap(",
" String::getBytes,",
" String::length,",
" (a, b) -> {",
" throw new IllegalStateException();",
" },",
" HashMap::new));",
" Stream.of(\"bar\")",
" .collect(",
" toMap(",
" String::getBytes,",
" String::length,",
" (a, b) -> {",
" throw new IllegalStateException();",
" },",
" HashMap::new));",
" Flux.just(\"baz\")",
" .collect(Collectors.toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
" Flux.just(\"qux\").collect(toMap(String::getBytes, String::length, (a, b) -> b, HashMap::new));",
"",
" Stream.of(1).collect(toCollection(HashSet::new));",
" Stream.of(2).collect(toCollection(HashSet::new));",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -5,14 +5,14 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class EmptyMethodTest {
public final class EmptyMethodCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(EmptyMethod.class, getClass());
CompilationTestHelper.newInstance(EmptyMethodCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());
BugCheckerRefactoringTestHelper.newInstance(new EmptyMethodCheck(), getClass());
@Test
void identification() {
public void testIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
@@ -33,10 +33,6 @@ final class EmptyMethodTest {
" interface F {",
" void fun();",
" }",
"",
" final class MyTestClass {",
" void helperMethod() {}",
" }",
"}")
.addSourceLines(
"B.java",
@@ -68,7 +64,7 @@ final class EmptyMethodTest {
}
@Test
void replacement() {
public void testReplacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",

View File

@@ -1,151 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ErrorProneTestHelperSourceFormatTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
ErrorProneTestHelperSourceFormat.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" // BUG: Diagnostic contains: No source code provided",
" .addSourceLines(\"A.java\")",
" // BUG: Diagnostic contains: Source code is malformed:",
" .addSourceLines(\"B.java\", \"class B {\")",
" // Well-formed code, so not flagged.",
" .addSourceLines(\"C.java\", \"class C {}\")",
" // Malformed code, but not compile-time constant, so not flagged.",
" .addSourceLines(\"D.java\", \"class D {\" + getClass())",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addSourceLines(\"E.java\", \"class E { }\")",
" .doTest();",
"",
" refactoringTestHelper",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addInputLines(\"in/A.java\", \"class A { }\")",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addOutputLines(\"out/A.java\", \"class A { }\")",
" // BUG: Diagnostic contains: Test code should follow the Google Java style",
" .addInputLines(\"in/B.java\", \"import java.util.Map;\", \"\", \"class B {}\")",
" // Unused import, but in an output file, so not flagged.",
" .addOutputLines(\"out/B.java\", \"import java.util.Map;\", \"\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
/*
* Verifies that import sorting and code formatting is performed unconditionally, while unused
* imports are removed unless part of a `BugCheckerRefactoringTestHelper` expected output file.
*/
refactoringTestHelper
.addInputLines(
"in/A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" .addSourceLines(",
" \"A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .doTest();",
"",
" refactoringTestHelper",
" .addInputLines(",
" \"in/A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .addOutputLines(",
" \"out/A.java\",",
" \"import java.util.Map;\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"\",",
" \"interface A extends List<A>, Map<A,A> { }\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.addOutputLines(
"out/A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.CompilationTestHelper;",
"import tech.picnic.errorprone.bugpatterns.EmptyMethod;",
"",
"class A {",
" private final CompilationTestHelper compilationTestHelper =",
" CompilationTestHelper.newInstance(EmptyMethod.class, getClass());",
" private final BugCheckerRefactoringTestHelper refactoringTestHelper =",
" BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());",
"",
" void m() {",
" compilationTestHelper",
" .addSourceLines(",
" \"A.java\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .doTest();",
"",
" refactoringTestHelper",
" .addInputLines(",
" \"in/A.java\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .addOutputLines(",
" \"out/A.java\",",
" \"import java.util.Collection;\",",
" \"import java.util.List;\",",
" \"import java.util.Map;\",",
" \"\",",
" \"interface A extends List<A>, Map<A, A> {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,85 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ExplicitEnumOrderingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static java.lang.annotation.RetentionPolicy.CLASS;",
"import static java.lang.annotation.RetentionPolicy.RUNTIME;",
"import static java.lang.annotation.RetentionPolicy.SOURCE;",
"import static java.time.chrono.IsoEra.BCE;",
"import static java.time.chrono.IsoEra.CE;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.Ordering;",
"import java.lang.annotation.RetentionPolicy;",
"import java.time.chrono.IsoEra;",
"",
"class A {",
" {",
" // The `List`-accepting overload is currently ignored.",
" Ordering.explicit(ImmutableList.of(RetentionPolicy.SOURCE, RetentionPolicy.CLASS));",
"",
" Ordering.explicit(IsoEra.BCE, IsoEra.CE);",
" // BUG: Diagnostic contains: IsoEra.CE",
" Ordering.explicit(IsoEra.BCE);",
" // BUG: Diagnostic contains: IsoEra.BCE",
" Ordering.explicit(IsoEra.CE);",
"",
" Ordering.explicit(RetentionPolicy.SOURCE, RetentionPolicy.CLASS, RetentionPolicy.RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS, RetentionPolicy.RUNTIME",
" Ordering.explicit(RetentionPolicy.SOURCE);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE, RetentionPolicy.RUNTIME",
" Ordering.explicit(RetentionPolicy.CLASS);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE, RetentionPolicy.CLASS",
" Ordering.explicit(RetentionPolicy.RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.RUNTIME",
" Ordering.explicit(RetentionPolicy.SOURCE, RetentionPolicy.CLASS);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS",
" Ordering.explicit(RetentionPolicy.SOURCE, RetentionPolicy.RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE",
" Ordering.explicit(RetentionPolicy.CLASS, RetentionPolicy.RUNTIME);",
"",
" Ordering.explicit(BCE, CE);",
" // BUG: Diagnostic contains: IsoEra.CE",
" Ordering.explicit(BCE);",
" // BUG: Diagnostic contains: IsoEra.BCE",
" Ordering.explicit(CE);",
"",
" Ordering.explicit(SOURCE, CLASS, RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS, RetentionPolicy.RUNTIME",
" Ordering.explicit(SOURCE);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE, RetentionPolicy.RUNTIME",
" Ordering.explicit(CLASS);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE, RetentionPolicy.CLASS",
" Ordering.explicit(RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.RUNTIME",
" Ordering.explicit(SOURCE, CLASS);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS",
" Ordering.explicit(SOURCE, RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE",
" Ordering.explicit(CLASS, RUNTIME);",
"",
" Ordering.explicit(RetentionPolicy.SOURCE, BCE, RetentionPolicy.CLASS, CE, RUNTIME);",
" Ordering.explicit(SOURCE, IsoEra.BCE, CLASS, IsoEra.CE, RetentionPolicy.RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS",
" Ordering.explicit(RetentionPolicy.SOURCE, BCE, CE, RUNTIME);",
" // BUG: Diagnostic contains: RetentionPolicy.CLASS",
" Ordering.explicit(IsoEra.BCE, SOURCE, IsoEra.CE, RetentionPolicy.RUNTIME);",
" // BUG: Diagnostic contains: IsoEra.CE, RetentionPolicy.RUNTIME",
" Ordering.explicit(IsoEra.BCE, SOURCE, RetentionPolicy.CLASS);",
" // BUG: Diagnostic contains: RetentionPolicy.SOURCE, IsoEra.BCE",
" Ordering.explicit(CLASS, RUNTIME, CE);",
" }",
"}")
.doTest();
}
}

Some files were not shown because too many files have changed in this diff Show More